<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 of.
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, and a new thread is created, or a thread which is not yet loaded is updated, the component will show a banner informing the user about the number of new threads, which the user can then click, 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, than 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 perfrom 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 perfrom on reaching the end of the list }The
ThreadListViewalso 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 themonlyif you areNOTusing thebindViewmethod to configure theThreadListView. UsingbindViewand calling any of these methods might result in unexpected behaviour. We recommend to always use thebindViewmethod and let the data shown by theThreadListViewbe managed by theThreadListViewModel.
Customization
There are multiple ways for customizing the apperance of the thread list:
Custom attributes
The ThreadListView exposes multiple attributes that can be overridden to
customize specific part 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 customize to 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, you would need to do the following 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 viewHodler 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 indtroduce new types of ViewHolders in attidion to the
existing ones, you would need to do the the following 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 viewHodler 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...
}
