<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>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.
The ThreadListView component is available on the UI components SDK since version 6.8.0.
Usage
The component can easily be used directly in an XML layout:
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:
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.
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 |
|---|
![]() |
| Thread list + new threads banner |
|---|
![]() |
The
bindViewmethod sets listeners on theViewand theViewModel. Any additional listeners should be set after callingbindView.
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 ownThreadListView.ThreadClickListenerto handle clicks on the items: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.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).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 passedList<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
bindViewmethod, so you should use them only if you are not using thebindViewmethod to configure theThreadListView. UsingbindViewand calling any of these methods might result in unexpected behavior. We recommend always using thebindViewmethod and letting the data shown by theThreadListViewbe managed by theThreadListViewModel.
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:
<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:
<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:
<!-- 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:
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 ViewHolders. For example, to provide a custom ViewHolder for rendering ThreadListItem.ThreadItem, follow these steps:
- implement a custom
ViewHolderby extending theBaseThreadListItemViewHolder - override the
ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)to ensure that the customViewHolderis properly mapped to the corresponding itemtype - override the
ThreadListItemViewHolderFactory.createThreadItemViewHolderso that it returns the customViewHolder - set the new
ThreadListItemViewHolderFactoryin theThreadListView
// 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
ViewHolderby extending theBaseThreadListItemViewHolder - override the
ThreadListItemViewHolderFactory.getItemViewType(ThreadListItem)to ensure that the customViewHolderis properly mapped to the corresponding itemtype - override the
ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)to ensure that the customViewHolderis properly mapped to the corresponding itemtype - override the
ThreadListItemViewHolderFactory.createViewHolderso that the new item type is properly mapped to the newViewHolder - set the new
ThreadListItemViewHolderFactoryin theThreadListView
// 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...
}
