# Channel List

The `ChannelList` component allows you to build a paginated list of `Channel` items with exposed long tap and single tap actions. We provide two versions of the `ChannelList` component:

- **Bound**: Binds to a `ChannelListViewModel` and loads all required data automatically. Connects long item tap and pagination events to the `ViewModel`.
- **Stateless**: Doesn't depend on a `ViewModel`. Instead, it takes `channelsState` and `currentUser` as parameters for rendering.

You can learn more about the different component types on the [Component Architecture](/chat/docs/sdk/android/v6/compose/component-architecture/) page.

<admonition type="note">

The **bound** version uses the **stateless** version internally. When providing the same state to either component, the behavior is identical.

</admonition>

Based on the provided state, the component shows the following UI:

- **Loading indicator**: When loading the initial data.
- **Empty content**: When loading is complete but there are no channels.
- **Empty search content**: When a search query returns no results.
- **Channel list**: The paginated list of channels.

Let's see how to show a list of channels.

## Usage

To use the **bound** `ChannelList`, add it to your UI within `setContent()`:

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ChatTheme {
            ChannelList(modifier = Modifier.fillMaxSize())
        }
    }
}
```

This basic example shows a paginated list of channels. The default `ChannelListViewModel` is created automatically with:

- Sort order: `QuerySortByField.descByName("last_updated")`
- Filters: `null` (shows all channels the user is a member of)

We recommend passing in action handlers to react to item taps, or providing a custom `ChannelListViewModel` instance.

The snippet above will generate the following UI.

| ![The Default ChannelList Component](@chat-sdk/android/v6/_assets/compose_default_channel_list_component.png) |
| ------------------------------------------------------------------------------------------------------------- |

Let's see how to handle actions and use the ViewModel.

## Handling Actions

If you've chosen the **bound** version of the `ChannelList` component, we recommend either providing your own instance of the `ViewModel`, or overriding default actions to react to state changes. To support that, the `ChannelList` signature exposes the following parameters:

```kotlin
@Composable
fun ChannelList(
    viewModel: ChannelListViewModel = viewModel(
        factory = ... // Our default factory
    ),
    onLastItemReached: () -> Unit = { viewModel.loadMore() },
    onChannelClick: (Channel) -> Unit = {},
    onChannelLongClick: (Channel) -> Unit = { viewModel.selectChannel(it) },
    onSearchResultClick: (Message) -> Unit = {},
    ... // Content Slots
)
```

- `viewModel`: The instance of the `ChannelListViewModel` that this component reads data from and sends events to. Pass in your own instance if you want more control over your business logic, such as changing `Channel` filters or sort order in runtime.
- `onLastItemReached`: Handler when the user reaches the last item in the list to trigger pagination. You don't need to override this if you're using the default `viewModel`, but if you're using a custom one, you can add custom behavior.
- `onChannelClick`: Handler when the user taps on an item. Useful for starting the `MessagesScreen`.
- `onChannelLongClick`: Handler when the user long taps on an item. By default, this updates state in the `viewModel`, which you can read to show custom UI and `Channel` actions if you're using a custom `ViewModel` instance. Override if you're using the default `viewModel` and you want to change the behavior.
- `onSearchResultClick`: Handler when the user taps on a search result item. Useful for starting the `MessagesScreen` with the selected message.

Here's an example of using the default `ViewModel`, but overriding the behavior:

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ChatTheme {
            // Custom state holder
            var selectedChannel by remember { mutableStateOf<Channel?>(null) }

            Box(modifier = Modifier.fillMaxSize()) {
                ChannelList(
                    modifier = Modifier.fillMaxSize(),
                    onChannelLongClick = { // Custom long tap handler
                        selectedChannel = it
                    },
                    onChannelClick = {
                        // Start the MessagesScreen
                    },
                    onSearchResultClick = {
                        // Start the MessagesScreen with the selected message
                    }
                )

                if (selectedChannel != null) {
                    // Show custom UI
                }
            }
        }
    }
}
```

In the example above, we created a `selectedChannel` state holder, which we use to show some custom UI if the data is not null. We update the state when the user long taps on an item.

We also provide a custom `onChannelClick` handler, to open the `MessagesScreen` with the selected item. This will produce the same UI, but with user-defined actions.
Finally we provide a custom `onSearchResultClick` handler, to open the `MessagesScreen` with the selected message.

Alternatively, you can override the default `ViewModel` and read the internal state:

```kotlin
val listViewModel: ChannelListViewModel by viewModels { ChannelViewModelFactory() }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ChatTheme {
            Box(modifier = Modifier.fillMaxSize()) {
                ChannelList(
                    modifier = Modifier.fillMaxSize(),
                    viewModel = listViewModel, // Passing in our ViewModel
                    onChannelClick = {
                        // Start the MessagesScreen
                    },
                    onSearchResultClick = {
                        // Start the MessagesScreen with the selected message
                    },
                )

                if (listViewModel.selectedChannel != null) {
                    // Show custom UI
                }
            }
        }
    }
}
```

The behavior will be the same and you gain more control over the `ViewModel`.

We recommend that you create an instance of our `ViewModel` if you're thinking of using our predefined state and operations. If you're looking into a more low-level solution, with more control, you can use the **stateless** version of our components.

<admonition type="tip">

To customize how real-time events affect the channel list (e.g., when using multiple lists filtered by channel type), see [Channels State and Filtering](/chat/docs/sdk/android/v6/client/guides/channels/).

</admonition>

### Controlling the scroll state

You can control the scroll state of the channel list by providing a `lazyListState` parameter, like in the example below:

```kotlin
@Composable
fun ChannelList(
    ..., // State
    lazyListState: LazyListState = rememberLazyListState(),
    ... // Actions & Content Slots
)
```

- `lazyListState`: The scroll state of the list. While not a handler, you can use it to control the scroll and trigger custom scroll actions.

To customize this state you can simply pass your own instance as a parameter:

```kotlin
 val lazyListState = rememberLazyListState()

ChannelList(
    // State
    lazyListState = lazyListState
    // Actions & Content Slots
)
```

## Customization

The `ChannelList` exposes several content slots for UI customization:

```kotlin
@Composable
fun ChannelList(
    // State and action handlers
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = PaddingValues(),
    lazyListState: LazyListState = rememberLazyListState(),
    loadingContent: @Composable () -> Unit = { /* Default loading indicator */ },
    emptyContent: @Composable () -> Unit = { /* Default empty state */ },
    emptySearchContent: @Composable (String) -> Unit = { /* Default empty search state */ },
    helperContent: @Composable BoxScope.() -> Unit = { /* Empty by default */ },
    loadingMoreContent: @Composable LazyItemScope.() -> Unit = { /* Default pagination loader */ },
    channelContent: @Composable LazyItemScope.(ItemState.ChannelItemState) -> Unit = { /* Default channel item */ },
    searchResultContent: @Composable LazyItemScope.(ItemState.SearchResultItemState) -> Unit = { /* Default search item */ },
    divider: @Composable LazyItemScope.() -> Unit = { /* Default divider */ },
)
```

- `modifier`: Modifier for the root component. Apply background, elevation, padding, shape, or touch handlers.
- `contentPadding`: Padding values applied around the list content.
- `lazyListState`: Controls the scroll state of the list. Useful for implementing scroll-to-top functionality.
- `loadingContent`: Displayed when loading the initial data.
- `emptyContent`: Displayed when there are no channels.
- `emptySearchContent`: Displayed when a search query returns no results. Receives the search query as a parameter.
- `helperContent`: Overlay content for the list. Empty by default, but useful for scroll-to-top buttons or other floating UI.
- `loadingMoreContent`: Displayed at the bottom of the list when loading the next page.
- `channelContent`: Customizes channel item rendering. Receives `ItemState.ChannelItemState` containing the channel and typing users.
- `searchResultContent`: Customizes search result item rendering. Receives `ItemState.SearchResultItemState` containing the message and channel.
- `divider`: Customizes the divider between items.

Here's a simple example for building your own channel item, by overriding the `channelContent` parameter:

```kotlin
val user by listViewModel.user.collectAsState() // Fetch user

ChannelList(
    ..., // Set up state
    channelContent = { // Customize the channel items
        Row(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            ChannelAvatar(
                modifier = Modifier.size(40.dp),
                channel = it.channel,
                currentUser = user
            )

            Spacer(modifier = Modifier.width(8.dp))

            Text(
                text = ChatTheme.channelNameFormatter.formatChannelName(it.channel, user),
                style = ChatTheme.typography.bodyBold,
                maxLines = 1,
            )
        }
    }
)
```

The snippet above will generate the following UI:

| ![Customized ChannelItem Component](@chat-sdk/android/v6/_assets/compose_custom_channel_list_item.png) |
| ------------------------------------------------------------------------------------------------------ |

As you can see, the items now show just the image and the channel name. You can customize the items to any extent, whatever your design specification might require.

And you can customize the `emptyContent` and the `loadingContent` to your needs if you need custom UI there.


---

This page was last updated at 2026-04-17T17:33:31.546Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/v6/compose/channel-components/channel-list/](https://getstream.io/chat/docs/sdk/android/v6/compose/channel-components/channel-list/).