# 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.