# Thread List

`ThreadListView` is an XML UI component which shows an overview of all threads of which the user is a member. It shows information about the channel, the thread parent message, the most recent reply in the thread, and the number of unread replies.

The component is paginated by default, and only the most recently updated threads are loaded initially. Older threads are loaded only when the user scrolls to the end of the thread list.

While this component is visible, if a new thread is created or a thread that is not yet loaded is updated, the component displays a banner informing the user about the number of new threads. The user can then click this banner to reload the thread list and load the newly updated threads.

<admonition type="note">

The `ThreadListView` component is available on the UI components SDK since version [6.8.0](https://github.com/GetStream/stream-chat-android/releases/tag/v6.8.0).

</admonition>

## Usage

The component can easily be used directly in an XML layout:

```xml
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <!-- Other views... -->

    <io.getstream.chat.android.ui.feature.threads.list.ThreadListView
        android:id="@+id/threadListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <!-- Other views... -->

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

You can use the `ThreadListViewModel` as a convenient way to populate the data for the `ThreadListView`. To instantiate such `ViewModel`, you need to first create an instance of `ThreadsViewModelFactory`:

```kotlin
private val threadsViewModelFactory by lazy {
    val query = QueryThreadsRequest(
        filter = /* ... */,
        sort = /* ... */,
        watch = /* ... */,
        limit = /* ... */,
        memberLimit = /* ... */,
        participantLimit = /* ... */,
        replyLimit = /* ... */,
    )
    ThreadsViewModelFactory(query)
}
```

The `ThreadsViewModelFactory` accepts a single `QueryThreadsRequest` parameter, which allows configuration of the following options:

- `filter` - The filter for the threads query (default: no filter)
- `sort` - The sorting order for the threads query (default: sort by unread status, then by last message timestamp, then by message ID in descending order)
- `watch` - Whether to watch the threads and the related channels for new events (default: `true`)
- `limit` - Maximum number of threads to be loaded per page (default: `10`)
- `memberLimit` - Number of members to request per thread channel (default: `100`)
- `participantLimit` - Maximum number of participants to be loaded per thread (default: `100`)
- `replyLimit` - Maximum number of (latest) replies to be loaded per thread (default: `2`)

After the `ThreadsViewModelFactory` is configured, you can instantiate the `ThreadListViewModel`, and bind it to the `ThreadListView` via the `bindView` method.

```kotlin
private val viewModel: ThreadListViewModel by viewModels {
    threadsViewModelFactory
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.bindView(binding.threadListView, viewLifecycleOwner)
    // Other views setup...
}
```

A `ThreadListView` bound to the `ThreadListViewModel` via the `bindView` method will produce a fully working thread list, together with a loading state, and an empty state for the case without threads:

| No threads                                                               |
| ------------------------------------------------------------------------ |
| ![Empty](@chat-sdk/android/v6/_assets/xml_default_thread_list_empty.jpg) |

| Thread list + new threads banner                                            |
| --------------------------------------------------------------------------- |
| ![Loaded](@chat-sdk/android/v6/_assets/xml_default_thread_list_content.jpg) |

> The `bindView` method sets listeners on the `View` and the `ViewModel`. Any additional listeners should be set after calling `bindView`.

## Handling actions

The `ThreadListView` exposes several configurable user actions:

- `setThreadClickListener` - Sets the click listener of the thread items in the list. By default, this action is empty, so you need to provide your own `ThreadListView.ThreadClickListener` to handle clicks on the items:

  ```kotlin
    threadListView.setThreadClickListener { thread: Thread ->
        // Handle click on the given Thread
    }
  ```

- `setUnreadThreadsBannerClickListener` - Sets the click listener of the unread threads banner. By default, clicking the banner results in reloading the whole thread list.

  ```kotlin
    threadListView.setUnreadThreadsBannerClickListener {
        // Note: By default, clicking the banner results in reloading the
        // whole thread list.
        // Make sure to call viewModel::load if you want to keep the
        // default behaviour in addition to adding your own.
        viewModel.load()
        // Additional action to perform on unread threads banner click
    }
  ```

- `setLoadMoreListener` - Sets the listener for reaching the end of the thread list, indicating that more threads should be loaded. By default, this will load the next page of threads (if available).

  ```kotlin
    binding.threadListView.setLoadMoreListener {
        // Note: By default, reaching the end of the lists results in loading
        // the next page of threads.
        // Make sure to call viewModel::loadNextPage if you want to keep the
        // default behaviour in addition to adding your own.
        viewModel.loadNextPage()
        // Additional action to perform on reaching the end of the list
    }
  ```

The `ThreadListView` also exposes several methods which allow you to directly modify the data displayed in the view:

- `showThreads(threads: List<Thread>, isLoadingMore: Boolean)` - Overrides the currently shown thread list with the passed `List<Thread>`.
- `showLoading()` - Clears the currently shown thread list and shows the loading state of the list.
- `showUnreadThreadsBanner(unreadThreadsCount: Int)` - Shows the unread threads banner.

> These methods are used internally in the `bindView` method, so you should use them only if you are not using the `bindView` method to configure the `ThreadListView`. Using `bindView` and calling any of these methods might result in unexpected behavior. We recommend always using the `bindView` method and letting the data shown by the `ThreadListView` be managed by the `ThreadListViewModel`.

## Customization

There are multiple ways for customizing the appearance of the thread list:

### Custom attributes

The `ThreadListView` exposes multiple attributes that can be overridden to customize specific parts of the list. For example, you can override the `app:streamUiThreadListBackground` to provide a custom background color for the thread list:

```xml
<io.getstream.chat.android.ui.feature.threads.list.ThreadListView
        android:id="@+id/threadListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:streamUiThreadListBackground="@color/app_background"
        />
```

### Custom theme

A different way to customize the `ThreadListView` is to create a custom theme extending from the `StreamUi.ThreadList` style:

```xml
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <!-- Set the streamUiTheme to the custom StreamTheme -->
    <item name="streamUiTheme">@style/StreamTheme</item>
</style>

<style name="StreamTheme" parent="@style/StreamUiTheme">
    <!-- Set the streamUiThreadList style to the custom ThreadListTheme -->
    <item name="streamUiThreadListStyle">@style/ThreadListTheme</item>
</style>

<style name="ThreadListTheme" parent="StreamUi.ThreadList">
    <!-- Other ThreadList properties... -->
    <item name="streamUiThreadListBackground">@color/app_background</item>
</style>
```

The list of configurable properties via custom `StreamUi.ThreadList` theme (or by using custom `ThreadListView` attributes) includes:

```xml
<!-- General customization -->
<!-- Background of the list -->
<item name="streamUiThreadListBackground">@color/stream_ui_white_snow</item>

<!-- Empty state customization -->
<!-- Drawable shown when there are no threads -->
<item name="streamUiThreadListEmptyStateDrawable">@drawable/stream_ui_ic_threads_empty</item>
 <!-- Text shown when there are no threads -->
<item name="streamUiThreadListEmptyStateText">@string/stream_ui_thread_list_empty_title</item>
 <!-- Color of the text shown when there are no threads -->
<item name="streamUiThreadListEmptyStateTextColor">@color/stream_ui_text_color_secondary</item>
 <!-- Size of the text shown when there are no threads -->
<item name="streamUiThreadListEmptyStateTextSize">@dimen/stream_ui_text_large</item>
 <!-- Style of the text shown when there are no threads (normal/bold/italic) -->
<item name="streamUiThreadListEmptyStateTextStyle">normal</item>

<!-- Results customization -->
<!-- Drawable for the thread icon -->
<item name="streamUiThreadListThreadIconDrawable">@drawable/stream_ui_ic_thread</item>
<!-- Color of the thread title -->
<item name="streamUiThreadListThreadTitleTextColor">@color/stream_ui_text_color_primary</item>
<!-- Size of the thread title -->
<item name="streamUiThreadListThreadTitleTextSize">@dimen/stream_ui_text_medium</item>
<!-- Style of the thread title (normal/bold/italic) -->
<item name="streamUiThreadListThreadTitleTextStyle">bold</item>
<!-- Color of the thread parent message text -->
<item name="streamUiThreadListThreadReplyToTextColor">@color/stream_ui_text_color_secondary</item>
<!-- Size of the thread parent message text -->
<item name="streamUiThreadListThreadReplyToTextSize">@dimen/stream_ui_text_medium</item>
<!-- Style of the thread parent message text (normal/bold/italic) -->
<item name="streamUiThreadListThreadReplyToTextStyle">normal</item>
<!-- Color of the latest reply sender name -->
<item name="streamUiThreadListThreadLatestReplySenderTextColor">@color/stream_ui_text_color_primary</item>
<!-- Size of the latest reply sender name -->
<item name="streamUiThreadListThreadLatestReplySenderTextSize">@dimen/stream_ui_text_medium</item>
<!-- Style of the latest reply sender name (normal/bold/italic) -->
<item name="streamUiThreadListThreadLatestReplySenderTextStyle">bold</item>
<!-- Color of the latest reply message text -->
<item name="streamUiThreadListThreadLatestReplyMessageTextColor">@color/stream_ui_text_color_secondary</item>
<!-- Size of the latest reply message text -->
<item name="streamUiThreadListThreadLatestReplyMessageTextSize">@dimen/stream_ui_text_medium</item>
<!-- Style of the latest reply message text (normal/bold/italic) -->
<item name="streamUiThreadListThreadLatestReplyMessageTextStyle">normal</item>
<!-- Color of the latest reply timestamp -->
<item name="streamUiThreadListThreadLatestReplyTimeTextColor">@color/stream_ui_text_color_secondary</item>
<!-- Size of the latest reply timestamp -->
<item name="streamUiThreadListThreadLatestReplyTimeTextSize">@dimen/stream_ui_text_medium</item>
<!-- Style of the latest reply timestamp (normal/bold/italic) -->
<item name="streamUiThreadListThreadLatestReplyTimeTextStyle">normal</item>
<!-- Color of the unread count badge text -->
<item name="streamUiThreadListThreadUnreadCountBadgeTextColor">@color/stream_ui_literal_white</item>
<!-- Size of the unread count badge text -->
<item name="streamUiThreadListThreadUnreadCountBadgeTextSize">@dimen/stream_ui_text_small</item>
<!-- Style of the unread count badge text (normal/bold/italic) -->
<item name="streamUiThreadListThreadUnreadCountBadgeTextStyle">normal</item>
<!-- Background of the unread count badge -->
<item name="streamUiThreadListThreadUnreadCountBadgeBackground">@drawable/stream_ui_shape_badge_background</item>

<!-- Unread Threads Banner customization -->
<!-- Color of the banner text -->
<item name="streamUiThreadListUnreadThreadsBannerTextColor">@color/stream_ui_white</item>
<!-- Size of the banner text -->
<item name="streamUiThreadListUnreadThreadsBannerTextSize">@dimen/stream_ui_text_large</item>
<!-- Style of the banner text (normal/bold/italic) -->
<item name="streamUiThreadListUnreadThreadsBannerTextStyle">normal</item>
<!-- Icon shown at the end of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerIcon">@drawable/stream_ui_ic_union</item>
<!-- Background of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerBackground">@drawable/stream_ui_shape_unread_threads_banner</item>
<!-- Left padding of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerPaddingLeft">@dimen/stream_ui_spacing_medium</item>
<!-- Top padding of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerPaddingTop">@dimen/stream_ui_spacing_medium</item>
<!-- Right padding of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerPaddingRight">@dimen/stream_ui_spacing_medium</item>
<!-- Bottom padding of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerPaddingBottom">@dimen/stream_ui_spacing_medium</item>
<!-- Left margin of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerMarginLeft">@dimen/stream_ui_spacing_small</item>
<!-- Top margin of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerMarginTop">@dimen/stream_ui_spacing_small</item>
<!-- Right margin of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerMarginRight">@dimen/stream_ui_spacing_small</item>
<!-- Bottom margin of the banner -->
<item name="streamUiThreadListUnreadThreadsBannerMarginBottom">@dimen/stream_ui_spacing_small</item>
```

### Style transformer

Another way of customizing the `ThreadListView` is by providing a custom `StyleTransformer` for modifying the default `ThreadListViewStyle`:

```kotlin
TransformStyle.threadListViewStyle = StyleTransformer { defaultStyle ->
    val backgroundColor = ContextCompat.getColor(context, R.color.app_background)
    defaultStyle.copy(
        backgroundColor = backgroundColor,
        // Override other attributes...
    )
}
```

### ThreadListItemViewHolderFactory

If the style customization is not enough for your needs, you can also customize the `ThreadListView` by overriding the `ThreadListItemViewHolderFactory`. By providing a custom implementation of `ThreadListItemViewHolderFactory`, you can override the default list items, or even add new types of `ViewHolder`s. For example, to provide a custom `ViewHolder` for rendering `ThreadListItem.ThreadItem`, follow these steps:

- implement a custom `ViewHolder` by extending the `BaseThreadListItemViewHolder`
- override the `ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)` to ensure that the custom `ViewHolder` is properly mapped to the corresponding item `type`
- override the `ThreadListItemViewHolderFactory.createThreadItemViewHolder` so that it returns the custom `ViewHolder`
- set the new `ThreadListItemViewHolderFactory` in the `ThreadListView`

```kotlin
// Create custom ViewHolder
class CustomThreadViewHolder(
    parentView: ViewGroup,
    val clickListener: ThreadListView.ThreadClickListener?,
    val binding: CustomThreadItemBinding = CustomThreadItemBinding.inflate(
        LayoutInflater.from(parentView.context),
        parentView,
        false
    ),
): BaseThreadListItemViewHolder<ThreadListItem.ThreadItem>(binding.root) {
    override fun bind(item: ThreadListItem.ThreadItem) {
        // Custom bind logic
    }
}

// Create custom ViewHolderFactory
class CustomThreadItemViewHolderFactory : ThreadListItemViewHolderFactory() {

    // Ensure the new viewHolder type is mapped to the proper type
    override fun getItemViewType(
      viewHolder: BaseThreadListItemViewHolder<out ThreadListItem>
    ): Int {
        return when (viewHolder) {
            is CustomThreadViewHolder -> ThreadListItemViewType.ITEM_THREAD
            else -> super.getItemViewType(viewHolder)
        }
    }

    // Ensure the custom ViewHolder is returned from its factory method.
    override fun createThreadItemViewHolder(
      parentView: ViewGroup
    ): BaseThreadListItemViewHolder<ThreadListItem.ThreadItem> {
        return CustomThreadViewHolder(parentView, clickListener)
    }
}

// Set the ViewHolderFactory
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding.threadListView.setViewHolderFactory(CustomThreadItemViewHolderFactory())
    viewModel.bindView(binding.threadListView, viewLifecycleOwner)
    // Other setup...
}
```

If you would like to introduce new types of `ViewHolders` in addition to the existing ones, follow these steps:

- implement a custom `ViewHolder` by extending the `BaseThreadListItemViewHolder`
- override the `ThreadListItemViewHolderFactory.getItemViewType(ThreadListItem)` to ensure that the custom `ViewHolder` is properly mapped to the corresponding item `type`
- override the `ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)` to ensure that the custom `ViewHolder` is properly mapped to the corresponding item `type`
- override the `ThreadListItemViewHolderFactory.createViewHolder` so that the new item type is properly mapped to the new `ViewHolder`
- set the new `ThreadListItemViewHolderFactory` in the `ThreadListView`

```kotlin
// Create the custom ViewHolder type
class SingleReplyThreadItemViewHolder(
    parentView: ViewGroup,
    val binding: SingleReplyThreadItemBinding = SingleReplyThreadItemBinding.inflate(
        LayoutInflater.from(parentView.context),
        parentView,
        false
    ),
): BaseThreadListItemViewHolder<ThreadListItem.ThreadItem>(binding.root) {
    override fun bind(item: ThreadListItem.ThreadItem) {
        // Custom bind logic
    }
}

// Create custom ViewHolderFactory
class CustomThreadItemViewHolderFactory : ThreadListItemViewHolderFactory() {

    companion object {
        // Define the new item type
        private const val SINGLE_REPLY_THREAD_ITEM_TYPE = 1001
    }

    // Ensure the new type is properly resolved
    override fun getItemViewType(item: ThreadListItem): Int {
        return when (item) {
            is ThreadListItem.ThreadItem -> {
                if (item.thread.replyCount == 1) {
                    SINGLE_REPLY_THREAD_ITEM_TYPE
                } else {
                    super.getItemViewType(item)
                }
            }
            else -> super.getItemViewType(item)
        }
    }

    // Ensure the new viewHolder type is mapped to the proper type
    override fun getItemViewType(
      viewHolder: BaseThreadListItemViewHolder<out ThreadListItem>
    ): Int {
        return when (viewHolder) {
            is SingleReplyThreadItemViewHolder -> SINGLE_REPLY_THREAD_ITEM_TYPE
            else -> super.getItemViewType(viewHolder)
        }
    }

    // Map the new ViewHolder to the new item type
    override fun createViewHolder(
        parentView: ViewGroup,
        viewType: Int
    ): BaseThreadListItemViewHolder<out ThreadListItem> {
        return when (viewType) {
            SINGLE_REPLY_THREAD_ITEM_TYPE -> SingleReplyThreadItemViewHolder(parentView)
            else -> super.createViewHolder(parentView, viewType)
        }
    }
}

// Set the ViewHolderFactory
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding.threadListView.setViewHolderFactory(CustomThreadItemViewHolderFactory())
    viewModel.bindView(binding.threadListView, viewLifecycleOwner)
    // Other setup...
}
```


---

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

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