<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 {
ThreadsViewModelFactory(
threadLimit = /* ... */,
threadReplyLimit = /* ... */,
threadParticipantLimit = /* ... */
)
}
The ThreadsViewModelFactory
accepts three configurable parameters:
threadLimit
- The maximum number of threads to be loaded per page (default:25
).threadReplyLimit
- The maximum number of (latest) replies to be loaded per thread (default:10
).threadParticipantLimit
- The maximum number of participants to be loaded per thread (default:10
).
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
bindView
method sets listeners on theView
and 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.ThreadClickListener
to 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
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
bindView
method, so you should use themonly
if you areNOT
using thebindView
method to configure theThreadListView
. UsingbindView
and calling any of these methods might result in unexpected behaviour. We recommend to always use thebindView
method and let the data shown by theThreadListView
be 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 ViewHolder
s.
For example, to provide a custom ViewHolder
for rendering
ThreadListItem.ThreadItem
, you would need to do the following steps:
- implement a custom
ViewHolder
by extending theBaseThreadListItemViewHolder
- override the
ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)
to ensure that the customViewHolder
is properly mapped to the corresponding itemtype
- override the
ThreadListItemViewHolderFactory.createThreadItemViewHolder
so that it returns the customViewHolder
- set the new
ThreadListItemViewHolderFactory
in 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
ViewHolder
by extending theBaseThreadListItemViewHolder
- override the
ThreadListItemViewHolderFactory.getItemViewType(ThreadListItem)
to ensure that the customViewHolder
is properly mapped to the corresponding itemtype
- override the
ThreadListItemViewHolderFactory.getItemViewType(BaseThreadListItemViewHolder)
to ensure that the customViewHolder
is properly mapped to the corresponding itemtype
- override the
ThreadListItemViewHolderFactory.createViewHolder
so that the new item type is properly mapped to the newViewHolder
- set the new
ThreadListItemViewHolderFactory
in 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...
}