# Building a Message List Screen

The SDK provides multiple UI Components which can be used together to build a message list screen. This guide will show you how to combine and customize [`ChannelHeaderView`](https://getstream.io/chat/docs/sdk/android/ui/message-components/channel-header/), [`MessageListView`](https://getstream.io/chat/docs/sdk/android/ui/message-components/message-list/), and [`MessageComposerView`](https://getstream.io/chat/docs/sdk/android/ui/message-components/message-composer/).

This is what a screen made with these three components looks like:

| Light Mode                                                             | Dark Mode                                                            |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------- |
| ![Light mode](https://getstream.io/docs-assets/images/9b4545e9742d.png) | ![Dark mode](https://getstream.io/docs-assets/images/ed1e4d674dfd.png) |

>
> **Note:** You can find the full code from this guide on [GitHub](https://github.com/GetStream/stream-chat-android/tree/main/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/uicomponents/messagesscreen). To check the final result, clone the repository, select the `stream-chat-android-ui-guides` module on your Android Studio like the image below, and run the module. ![UI Guides Module on Android Studio](https://getstream.io/docs-assets/images/8a5f408dd101.png)
>

To add these Views to your app, first create them in an XML layout:

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <io.getstream.chat.android.ui.feature.messages.header.ChannelHeaderView
        android:id="@+id/channelHeaderView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <io.getstream.chat.android.ui.feature.messages.list.MessageListView
        android:id="@+id/messageListView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:clipToPadding="false"
        app:layout_constraintBottom_toTopOf="@+id/messageComposerView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/channelHeaderView" />

    <io.getstream.chat.android.ui.feature.messages.composer.MessageComposerView
        android:id="@+id/messageComposerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/messageListView" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

Just like other components, these three views come with [ViewModels](https://getstream.io/chat/docs/sdk/android/ui/overview#viewmodels) which are responsible for providing all necessary data for them.

After setting up the ViewModels, this screen also requires some additional setup to pass information between the different chat components.

**Kotlin**

```kotlin
// Create ViewModels for the Views
val factory = ChannelViewModelFactory(context = requireContext(), cid = "messaging:123")
val channelHeaderViewModel: ChannelHeaderViewModel by viewModels { factory }
val messageListViewModel: MessageListViewModel by viewModels { factory }
val messageComposerViewModel: MessageComposerViewModel by viewModels { factory }

// Bind the ViewModels with the Views
channelHeaderViewModel.bindView(channelHeaderView, viewLifecycleOwner)
messageListViewModel.bindView(messageListView, viewLifecycleOwner)
messageComposerViewModel.bindView(messageComposerView, viewLifecycleOwner)

// Let both channel header and message input know when we open a thread
messageListViewModel.mode.observe(viewLifecycleOwner) { mode ->
    when (mode) {
        is MessageMode.MessageThread -> {
            channelHeaderViewModel.setActiveThread(mode.parentMessage)
            messageComposerViewModel.setMessageMode(MessageMode.MessageThread(mode.parentMessage))
        }
        is MessageMode.Normal -> {
            channelHeaderViewModel.resetThread()
            messageComposerViewModel.leaveThread()
        }
    }
}

// Let the message composer know when we are replying to a message
messageListView.setMessageReplyHandler { _, message ->
    messageComposerViewModel.performMessageAction(Reply(message))
}

// Let the message composer know when we are editing a message
messageListView.setMessageEditHandler { message ->
    messageComposerViewModel.performMessageAction(Edit(message))
}

// Handle navigate up state
messageListViewModel.state.observe(viewLifecycleOwner) { state ->
    if (state is MessageListViewModel.State.NavigateUp) {
        requireActivity().finish()
    }
}

// Handle back button behaviour correctly when you're in a thread
val backHandler = {
    messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed)
}
channelHeaderView.setBackButtonClickListener(backHandler)

// Override the default Activity's back button behaviour
requireActivity().onBackPressedDispatcher.addCallback(
    viewLifecycleOwner,
    object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            backHandler()
        }
    }
)
```

**Java**

```java
// Create ViewModels for the Views
ViewModelProvider.Factory factory = new ChannelViewModelFactory.Builder(requireContext())
        .cid("messaging:123")
        .build();
ViewModelProvider provider = new ViewModelProvider(this, factory);
ChannelHeaderViewModel channelHeaderViewModel = provider.get(ChannelHeaderViewModel.class);
MessageListViewModel messageListViewModel = provider.get(MessageListViewModel.class);
MessageComposerViewModel messageComposerViewModel = provider.get(MessageComposerViewModel.class);

// Bind the ViewModels with the Views
ChannelHeaderViewModelBinding.bind(channelHeaderViewModel, channelHeaderView, getViewLifecycleOwner());
MessageListViewModelBinding.bind(messageListViewModel, messageListView, getViewLifecycleOwner());
MessageComposerViewModelBinder.with(messageComposerViewModel).bind(messageComposerView, getViewLifecycleOwner());

// Let both channel header and message input know when we open a thread
messageListViewModel.getMode().observe(getViewLifecycleOwner(), mode -> {
    if (mode instanceof MessageMode.MessageThread) {
        Message parentMessage = ((MessageMode.MessageThread) mode).getParentMessage();
        channelHeaderViewModel.setActiveThread(parentMessage);
        messageComposerViewModel.setMessageMode(new MessageMode.MessageThread(parentMessage, null));
    } else if (mode instanceof MessageMode.Normal) {
        channelHeaderViewModel.resetThread();
        messageComposerViewModel.leaveThread();
    }
});

// Let the message composer know when we are replying to a message
messageListView.setMessageReplyHandler((cid, message) ->
    messageComposerViewModel.performMessageAction(new Reply(message))
);

// Let the message composer know when we are editing a message
messageListView.setMessageEditHandler(message ->
    messageComposerViewModel.performMessageAction(new Edit(message))
);

// Handle navigate up state
messageListViewModel.getState().observe(getViewLifecycleOwner(), state -> {
    if (state instanceof MessageListViewModel.State.NavigateUp) {
        requireActivity().finish();
    }
});

// Handle back button behaviour correctly when you're in a thread
ChannelHeaderView.OnClickListener backHandler = () -> {
    messageListViewModel.onEvent(MessageListViewModel.Event.BackButtonPressed.INSTANCE);
};
channelHeaderView.setBackButtonClickListener(backHandler);

// Override the default Activity's back button behaviour
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
    @Override
    public void handleOnBackPressed() {
        backHandler.onClick();
    }
});
```

>
> **Note:** `bindView` sets listeners on the View and the ViewModel. Any additional listeners should be set _after_ calling `bindView`.
>

This gives you a fully functional messaging screen, where you're able to display and send messages, and perform various actions in the message list.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/ui/guides/building-message-list-screen/](https://getstream.io/chat/docs/sdk/android/ui/guides/building-message-list-screen/).