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