# Message Center
Implement Message Center to provide an inbox for rich HTML-based messages, including display, theming, and building a custom UI.
# Get started with Message Center on Android
> Message Center provides an inbox for rich HTML-based messages that users can view at their convenience, with support for custom theming and display handling.

*Message Center inbox on Android*
Message Center provides an inbox for rich, HTML-based messages that users can view at their convenience. By default, when your app receives a push notification with a Message Center action, the Message Center automatically displays in a modal activity.
The Message Center can also be displayed manually by calling a simple method, making it easy to add a Message Center button to your app's navigation. Message Center inboxes are associated with channel IDs, which persist across app launches, allowing users to access their message history.
Airship provides two distinct modules for displaying Message Center on Android, depending on your app's UI framework:
- **`urbanairship-message-center-compose`**: Provides Compose-based Message Center UI components, for apps built with Jetpack Compose.
- **`urbanairship-message-center`**: Provides XML-based Message Center UI components, for apps using traditional Android Views (XML layouts).
You should select only one module based on your UI frameworkâdo not include both modules in the same app.
For more information about Message Center messages, see the [Message Center feature guide](https://www.airship.com/docs/guides/features/messaging/message-center/).
## Installation
Include the correct module for your chosen UI framework:
#### Jetpack Compose
```kotlin
dependencies {
val airshipVersion = "21.0.1"
implementation("com.urbanairship.android:urbanairship-message-center-compose:$airshipVersion")
}
```
#### XML Views
```kotlin
dependencies {
val airshipVersion = "21.0.1"
implementation("com.urbanairship.android:urbanairship-message-center:$airshipVersion")
}
```
## Display the Message Center
Display the Message Center with a single method call:
#### Kotlin
```kotlin
Airship.messageCenter.showMessageCenter()
```
#### Java
```java
MessageCenter.shared().showMessageCenter();
```
This displays the Message Center as a modal activity, allowing users to view and manage their messages. When the user closes the Message Center, any changes (such as marking messages as read) are automatically synced with Airship.
> **Note:** To build your own Message Center from the SDK's ready-to-use views, see [Build a custom Message Center](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/). You can also [intercept display requests](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/#handling-display-requests) to route them to your own UI.
## Applying a Custom Theme
You can customize the appearance of the Message Center to match your app's style. Android supports theme customization through both Jetpack Compose and XML Views.
#### Jetpack Compose
To apply a custom theme to the ready-to-use Message Center UI, create a theme and set it on the `MessageCenter` instance early in your app's lifecycle. The overridden `onAirshipReady()` method in your Autopilot class is a good place to do this.
**Customizing the theme with Jetpack Compose**
```kotlin
// Configure Message Center Theme
val messageCenterTheme = MessageCenterTheme(
lightColors = MessageCenterColors.lightDefaults(
background = Color(0xDEDEDE),
surface = Color(0xFFFFFF),
accent = Color(0x6200EE),
),
darkColors = MessageCenterColors.darkDefaults(
background = Color(0x121212),
surface = Color(0x1E1E1E),
accent = Color(0xBB86FC),
),
typography = MessageCenterTypography.defaults(
fontFamily = FontFamily(context.resources.getFont(R.font.roboto_regular))
)
)
// Apply theme to default Message Center UI
Airship.messageCenter.theme = messageCenterTheme
```
#### XML Views
The ready-to-use Message Center UI uses the `UrbanAirship.MessageCenter` style. You can use xml resource merging to override the default styles, by defining the style in your app.
### Theme Attributes
The Message Center supports the following theme attributes:
**messageCenterToolbarTitle**
: String to use for the Message Center toolbar title
**messageCenterIconsEnabled**
: Flag to enable message thumbnails in the message list
**messageCenterPlaceholderIcon**
: The default placeholder image for message thumbnails
**messageCenterItemDividersEnabled**
: Flag to enable dividers between messages in the list
**messageCenterItemDividerInsetStart**
: The start inset for message list dividers
**messageCenterItemDividerInsetEnd**
: The end inset for message list dividers
**dividerColor** (set via Material Theme)
: The message list divider color, if dividers are enabled
**Extending from a Material3 app theme**
```xml
```
> **Note:** If your app doesn't use a `Material3` theme or you need the ability to further customize Message Center styles, the Android resource merging feature can be used to override the default styles that the SDK provides. Copy the [style sheet](https://github.com/urbanairship/android-library/blob/master/urbanairship-message-center/src/main/res/values/style_message_center.xml)
> into the application's resource directory, then change any of the styles.
## Working with Messages
The Message Center provides methods to fetch, mark as read, and delete messages programmatically.
### Fetch Messages
Retrieve messages from the inbox:
#### Kotlin
```kotlin
// Suspending call
scope.launch {
val messages = Airship.messageCenter.inbox.getMessages()
}
// Flow
scope.launch {
// Collect the messages flow, which emits a new list whenever the inbox is updated
Airship.messageCenter.inbox.getMessagesFlow().collect { messages ->
// Handle messages
}
}
```
#### Java
```java
PendingResult> messagesResult = MessageCenter.shared().getInbox().getMessagesPendingResult();
messagesResult.addResultCallback(messages -> {
// Handle messages
});
```
### Listen for Message Updates
Subscribe to message updates using a listener or Flow:
#### Kotlin
```kotlin
// Option 1: Messages Flow
scope.launch {
Airship.messageCenter.inbox.getMessagesFlow().collect { messages ->
// Update your UI with the new messages
}
}
// Option 2: InboxListener
Airship.messageCenter.inbox.addListener(object: InboxListener {
override fun onInboxUpdated() {
// Update your UI
}
})
```
#### Java
```java
MessageCenter.shared().getInbox().addListener(() -> {
// Update your UI
});
```
### Listen for Unread Count Changes
Subscribe to unread count updates:
#### Kotlin
```kotlin
scope.launch {
Airship.messageCenter.inbox.getUnreadCountFlow().collect { unreadCount ->
// Update badge or UI
}
}
```
#### Java
```java
MessageCenter.shared().getInbox().getUnreadCountPendingResult()
.addResultCallback(unreadCount -> {
// Update badge or UI
});
```
### Refresh Messages
Manually refresh the message list from the server:
#### Kotlin
```kotlin
Airship.messageCenter.inbox.fetchMessages { success ->
// Handle result
}
```
#### Java
```java
MessageCenter.shared().getInbox().fetchMessages(new Inbox.FetchMessagesCallback() {
@Override
public void onFinished(boolean success) {
// Handle the result
}
});
```
### Mark Messages as Read
Mark one or more messages as read:
#### Kotlin
```kotlin
Airship.messageCenter.inbox.markMessagesRead(messageId)
```
#### Java
```java
MessageCenter.shared().getInbox().markMessagesRead("messageId");
```
### Delete Messages
Delete one or more messages:
#### Kotlin
```kotlin
Airship.messageCenter.inbox.deleteMessages("messageId")
```
#### Java
```java
MessageCenter.shared().getInbox().deleteMessages("messageId");
```
## Filter Messages by Named User
By default, Message Center displays all messages sent to the device's channel. If multiple users log into your app on the same device, they'll all see the same messages.
To filter messages by named user, set up filtering in your custom Message Center implementation. See [Filtering messages](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/#filtering-messages) in the custom Message Center guide.
When creating Message Center messages, include a custom key with `named_user_id` as the key and the user's actual ID as the value:
- **For the API**: Use the `extra` object in the [Message Center object](https://www.airship.com/docs/developer/rest-api/ua/schemas/push/#messageobject).
- **In the dashboard**: See [Custom keys](https://www.airship.com/docs/guides/messaging/messages/delivery/delivery-options/#custom-keys) and [Custom keys for Message Center](https://www.airship.com/docs/guides/messaging/messages/delivery/delivery-options/#custom-keys-message-center) in *Message delivery options*.
### Filtering Behavior
With named user filtering enabled:
- If you target `User A` in a message while they are logged in, the message appears in their inbox.
- If you target `User B` in a message while they are logged in, the message appears in their inbox.
- If you target `User A` or `User B` while the other is logged in, the message does not appear.
- If you target `User A` or `User B` while neither is logged in, the message does not appear.
## Build a custom Message Center
Build a custom Message Center by composing the SDK's ready-to-use components, handling display requests, integrating with your navigation, and filtering messages.
# Message Center inbox view
> Show the complete Message Center inbox using the MessageCenterScreen composable in Jetpack Compose or the MessageCenterFragment in XML Views.
The inbox view shows the complete Message Center: the message list and, when a message is selected, its content. On large displays and foldables it uses an adaptive two-pane layout, showing the list and the selected message side by side.
## Jetpack Compose
`MessageCenterScreen` is the complete Message Center. Wrap it in a `MessageCenterTheme`, which provides the values the Message Center composables read for colors, typography, and options.
#### Jetpack Compose
```kotlin
import com.urbanairship.messagecenter.compose.ui.MessageCenterScreen
import com.urbanairship.messagecenter.compose.ui.rememberMessageCenterState
import com.urbanairship.messagecenter.compose.ui.theme.MessageCenterTheme
@Composable
fun MyMessageCenterScreen() {
val state = rememberMessageCenterState()
MessageCenterTheme {
MessageCenterScreen(state = state)
}
}
```
The composable accepts a modifier, the Message Center state, and navigation options:
```kotlin
@Composable
public fun MessageCenterScreen(
modifier: Modifier = Modifier,
state: MessageCenterState = rememberMessageCenterState(),
showListNavigateUpIcon: Boolean = false,
onNavigateUp: () -> Unit = {}
)
```
- `showListNavigateUpIcon`: Whether to show a navigate-up icon in the list pane.
- `onNavigateUp`: Called when the navigate-up icon is tapped.
### State and filtering
`rememberMessageCenterState` creates the state that binds the list and message panes together. Pass a predicate to filter the list, or a message ID to display a message on first composition:
```kotlin
val state = rememberMessageCenterState(
predicate = Predicate { message -> message.title.contains("cool") }
)
```
Use the state to select or clear the displayed message programmatically:
```kotlin
state.selectMessage(messageId)
state.clearMessage()
```
### Theming
Wrap the screen in a `MessageCenterTheme` and pass colors, typography, and options. See [Applying a custom theme](https://www.airship.com/docs/developer/sdk-integration/android/message-center/getting-started/#applying-a-custom-theme) for the full set of theme values.
```kotlin
val options = MessageCenterOptions.defaults().copy(
messageCenterListTitle = "Inbox",
canDeleteMessages = true
)
MessageCenterTheme(
colors = if (isSystemInDarkTheme()) darkColors else lightColors,
options = options
) {
MessageCenterScreen(state = state)
}
```
## XML Views
Embed `MessageCenterFragment` in any `FragmentActivity` or `Fragment`. Add it directly in your layout with a `FragmentContainerView`:
```xml
```
Or create it programmatically. Use `newInstance` to open the fragment directly to a message:
#### Kotlin
```kotlin
// Show the message list
val fragment = MessageCenterFragment.newInstance(null)
// Or open directly to a message
val fragment = MessageCenterFragment.newInstance(messageId)
```
#### Java
```java
MessageCenterFragment fragment = MessageCenterFragment.newInstance(null);
```
To filter the list, set `listPredicate`:
```kotlin
fragment.listPredicate = Predicate { message -> message.title.contains("cool") }
```
The fragment uses a sliding pane layout that automatically supports single-pane and two-pane layouts on large displays and foldables. Right-to-left layout is supported when `android:supportsRtl="true"` is set in your manifest.
To respond to Message Center events, set a `Listener`:
```kotlin
fragment.listener = object : MessageCenterFragment.Listener {
override fun onShowMessage(message: Message): Boolean {
// Return true if you handled displaying the message,
// or false to use the default message view
return false
}
override fun onListEditModeChanged(isEditing: Boolean) {}
override fun onCloseMessage() {}
override fun onMessageLoaded(message: Message) {}
override fun onMessageLoadError(error: MessageViewState.Error.Type) {}
}
```
To navigate to your embedded fragment instead of letting the SDK launch its own activity, [handle display requests](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/#handling-display-requests).
# Message Center list view
> Show the Message Center message list on its own using the MessageCenterList composable in Jetpack Compose or the MessageCenterListFragment in XML Views.
The list view shows the Message Center inbox without the message detail. Use it when you provide your own message display, or when you want the list in one part of your app and the message in another. Handle selection yourself, typically by presenting a [message view](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/message-view/).
## Jetpack Compose
Two composables are available. `MessageCenterListScreen` includes a top bar and edit toolbar. `MessageCenterList` provides only the list content and edit toolbar, so you supply the top bar.
#### Jetpack Compose
```kotlin
import com.urbanairship.messagecenter.compose.ui.MessageCenterListScreen
import com.urbanairship.messagecenter.compose.ui.rememberMessageCenterListState
import com.urbanairship.messagecenter.compose.ui.theme.MessageCenterTheme
@Composable
fun MyListScreen(onMessageSelected: (Message) -> Unit) {
val state = rememberMessageCenterListState()
MessageCenterTheme {
MessageCenterListScreen(
state = state,
onMessageSelected = onMessageSelected
)
}
}
```
**MessageCenterListScreen**
```kotlin
@Composable
public fun MessageCenterListScreen(
modifier: Modifier = Modifier,
state: MessageCenterListState = rememberMessageCenterListState(),
topBar: @Composable ((onNavigateUp: () -> Unit) -> Unit)? = null,
onNavigateUp: () -> Unit = { },
onMessageSelected: (Message) -> Unit,
)
```
**MessageCenterList**
```kotlin
@Composable
public fun MessageCenterList(
state: MessageCenterListState = rememberMessageCenterListState(),
onMessageClick: (Message) -> Unit,
modifier: Modifier = Modifier,
)
```
Both composables render the loading, error, empty, and content states, pull-to-refresh, and the edit toolbar for selecting, marking read, and deleting messages. When `topBar` is null, `MessageCenterListScreen` uses a default top bar.
Pass a predicate to filter the list, or a highlighted message ID to mark the selected message in a two-pane layout:
```kotlin
val state = rememberMessageCenterListState(
predicate = Predicate { message -> message.title.contains("cool") },
highlightedMessageId = selectedMessageId
)
```
## XML Views
Subclass `MessageCenterListFragment` and set an `onMessageClickListener` to handle message selection. This example uses Jetpack Navigation, but any navigation method works.
```kotlin
class CustomMessageListFragment : MessageCenterListFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val toolbar = view.findViewById(R.id.toolbar)
toolbar.inflateMenu(messageCenterR.menu.ua_message_center_list_pane_menu)
setupWithNavController(toolbar, findNavController())
onMessageClickListener = OnMessageClickListener { message ->
// Navigate to the message fragment
findNavController().navigate(
R.id.action_messageCenterFragment_to_messageFragment,
bundleOf(
MessageCenterMessageFragment.ARG_MESSAGE_ID to message.id,
MessageCenterMessageFragment.ARG_MESSAGE_TITLE to message.title
)
)
}
}
}
```
To filter the list, set `predicate`:
```kotlin
predicate = Predicate { message -> message.title.contains("cool") }
```
Embed the fragment with a `FragmentContainerView` or through the `FragmentManager`, then [handle display requests](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/#handling-display-requests) to navigate to it instead of letting the SDK launch its own activity.
# Message Center message view
> Show a single Message Center message using the MessageCenterMessage composable in Jetpack Compose or the MessageCenterMessageFragment in XML Views.
The message view shows a single Message Center message. Use it alongside your own [list view](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/list-view/), or to deep link directly to a message.
## Jetpack Compose
Two composables are available. `MessageCenterMessageScreen` includes a top bar with a delete action. `MessageCenterMessage` provides only the message content, so you supply the top bar.
#### Jetpack Compose
```kotlin
import com.urbanairship.messagecenter.compose.ui.MessageCenterMessageScreen
import com.urbanairship.messagecenter.compose.ui.rememberMessageCenterMessageState
import com.urbanairship.messagecenter.compose.ui.theme.MessageCenterTheme
@Composable
fun MyMessageScreen(messageId: String, onClose: () -> Unit) {
val state = rememberMessageCenterMessageState(messageId = messageId)
MessageCenterTheme {
MessageCenterMessageScreen(
state = state,
onClose = onClose
)
}
}
```
**MessageCenterMessageScreen**
```kotlin
@Composable
public fun MessageCenterMessageScreen(
modifier: Modifier = Modifier,
state: MessageCenterMessageState = rememberMessageCenterMessageState(),
topBar: @Composable ((title: String?, scrollBehavior: TopAppBarScrollBehavior, onNavigateUp: () -> Unit) -> Unit)? = null,
onNavigateUp: () -> Unit = { },
onClose: () -> Unit = { }
)
```
**MessageCenterMessage**
```kotlin
@Composable
public fun MessageCenterMessage(
state: MessageCenterMessageState,
modifier: Modifier = Modifier,
onClose: () -> Unit,
)
```
- `onClose`: Called when the message is closed. `MessageCenterMessageScreen` also uses it as the delete callback.
- `topBar`: An optional top bar. When null, `MessageCenterMessageScreen` uses a default top bar with a delete action, which respects the `canDeleteMessages` [theme option](https://www.airship.com/docs/developer/sdk-integration/android/message-center/getting-started/#applying-a-custom-theme).
Set the message to display through the state. Update `messageId` to load a different message:
```kotlin
val state = rememberMessageCenterMessageState()
state.messageId = messageId
```
## XML Views
Subclass `MessageCenterMessageFragment` and set an `onMessageDeletedListener` to handle deletion. This example uses Jetpack Navigation, but any navigation method works.
```kotlin
class CustomMessageFragment : MessageCenterMessageFragment(R.layout.fragment_inbox_message) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar?.run {
inflateMenu(messageCenterR.menu.ua_message_center_message_pane_menu)
setupWithNavController(this, findNavController(view))
}
onMessageDeletedListener = OnMessageDeletedListener { message ->
// Navigate back to the list after deletion
findNavController().popBackStack()
}
}
}
```
Create the fragment with `newInstance`, passing the message ID and, optionally, a title to show until the message loads:
#### Kotlin
```kotlin
val fragment = MessageCenterMessageFragment.newInstance(messageId, messageTitle)
```
#### Java
```java
MessageCenterMessageFragment fragment =
MessageCenterMessageFragment.newInstance(messageId, messageTitle);
```
Embed the fragment with a `FragmentContainerView` or through the `FragmentManager`, then [handle display requests](https://www.airship.com/docs/developer/sdk-integration/android/message-center/custom/#handling-display-requests) to navigate to it instead of letting the SDK launch its own activity.