# 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](https://www.airship.com/docs/images/message-center-android.webp) *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.