Channel List View

Last Edit: Sep 22 2020

The ChannelListView shows a list of channels. By default it shows the channel name (in case a name isn't specified it will use the names of the channel members), user's read state, last message and the time of the last message:

You can use the custom view in your layout as shown below:


The channel list needs to know about your channelListViewModel in order to render the list of channels:

ChannelListViewModel viewModel = new ViewModelProvider(this).get(ChannelListViewModel.class);
// query all channels of type messaging
FilterObject filter = and(eq("type", "messaging"), in("members", "twilight-lab-0"));
ChannelListView channelList = findViewById(;
channelList.setViewModel(viewModel, this);

val viewModel = ViewModelProvider(this).get(
// query all channels of type messaging
val filter = and(eq("type", "messaging"), `in`("members", "summer-brook-2"))
val channelList = findViewById(
channelList.setViewModel(viewModel, this)

The setViewModel call connects the channel livedata objects from the viewmodel with the view. It also triggers the loadMore() method on the viewModel when reaching the bottom of the page.


The Channel List view model keeps channels in sync using the low level client's event system. The following events trigger a channel update:


  • message.updated

  • message.deleted

  • channel.updated

  • message.mark_read

  • member.added

  • member.updated

  • member.removed

Events that add new channels:

  • notification.message_new

  • notification.added_to_channel

Events that remove channels:

  • notification.removed_from_channel

  • channel.deleted

Customizing event handling

In some cases you'll want to customize how events are handled. Here's an example on how to ignore new channels when NOTIFICATION_MESSAGE_NEW has a message from a muted user

viewModel.setEventInterceptor((event, channel) -> {
    if (event.getType() == EventType.NOTIFICATION_MESSAGE_NEW && event.getMessage() != null) {
        return client.getUser().hasMuted(event.getMessage().getUser());
    return false;

viewModel.setEventInterceptor { event, channel ->
     var stopEvent = false;
     if (event.type == EventType.NOTIFICATION_MESSAGE_NEW) {
         stopEvent = client.user.hasMuted(event.message.user)


The following listeners can be set on the channel list view:

  • setOnChannelClickListener

  • setOnLongClickListener

  • setOnUserClickListener

Styling using Attributes

You can style this custom view using the following attributes:


Name Type Description Default Optional
app:streamAvatarWidth dimension 40dp
app:streamAvatarHeight dimension 40dp
app:streamAvatarBorderWidth dimension 3dp
app:streamAvatarBorderColor color WHITE
app:streamAvatarBackGroundColor color stream_gray_dark
app:streamAvatarTextSize dimension 14sp
app:streamAvatarTextColor color WHITE
app:streamAvatarTextStyle normal, bold, italic bold
app:streamAvatarTextFont reference -
app:streamAvatarTextFontAssets string -

Read State

Name Type Description Default Optional
app:streamShowReadState boolean true
app:streamReadStateAvatarWidth dimension 14dp
app:streamReadStateAvatarHeight dimension 14dp
app:streamReadStateTextSize dimension 8sp
app:streamReadStateTextColor color BLACK
app:streamReadStateTextStyle normal, bold, italic bold
app:streamReadStateTextFont reference -
app:streamReadStateTextFontAssets string -


Name Type Description Default Optional
app:streamChannelTitleTextSize dimension 15sp
app:streamChannelTitleTextColor color BLACK
app:streamChannelTitleUnreadTextColor color BLACK
app:streamChannelTitleTextStyle normal, bold, italic bold
app:streamChannelTitleUnreadTextStyle normal, bold, italic bold
app:streamChannelWithOutNameTitleText string Channel without name

Last Message

Name Type Description Default Optional
app:streamLastMessageTextSize dimension 13sp
app:streamLastMessageTextColor color stream_gray_dark
app:streamLastMessageUnreadTextColor color BLACK
app:streamLastMessageTextStyle normal, bold, italic normal
app:streamLastMessageUnreadTextStyle normal, bold, italic bold
app:streamLastMessageTextFont reference -
app:streamLastMessageTextFontAssets string -

Last Message Date

Name Type Description Default Optional
app:streamLastMessageDateTextSize dimension 11sp
app:streamLastMessageDateTextColor color stream_gray_dark
app:streamLastMessageDateUnreadTextColor color BLACK
app:streamLastMessageDateTextStyle normal, bold, italic normal
app:streamLastMessageDateUnreadTextStyle normal, bold, italic bold
app:streamLastMessageDateTextFont reference -
app:streamLastMessageDateTextFontAssets string -


Name Type Description Default Optional
app:streamChannelPreviewLayout reference _

Changing the layout

If you need to make a bigger change you can swap the layout for the channel previews. This approach only works for simple changes where you don't change the IDs of views, or their types. You can find the default layout in stream_item_channel.xml

    app:streamChannelPreviewLayout="@layout/list_item_channel_custom" />

Custom ViewHolder

You can create a custom view holder to make more complex changes to the layout and behaviour. This is how you tell the ChannelListView to use a custom view holder factory:

val channelList = findViewById(
val adapter = ChannelListItemAdapter(activity)
channelList.setViewModel(viewModel, this, adapter)

ChannelListView channelList = findViewById(;
ChannelListItemAdapter adapter = new ChannelListItemAdapter(this);
adapter.setViewHolderFactory(new CustomViewHolderFactory());
channelList.setViewModel(viewModel, this, adapter);

Here's an example view holder factory:

class CustomViewHolderFactory : ChannelViewHolderFactory() {
    override fun createChannelViewHolder(
        adapter: ChannelListItemAdapter,
        parent: ViewGroup,
        viewType: Int
    ): BaseChannelListItemViewHolder? { // get the style object
        val style =
        // inflate the layout specified in the style
        val v = LayoutInflater.from(parent.context)
            .inflate(style.channelPreviewLayout, parent, false)
        // configure the viewholder
        val holder = CustomChannelListItemViewHolder(v)
        configureHolder(holder, adapter)
        // return..
        return holder

public class CustomViewHolderFactory extends ChannelViewHolderFactory {
    public BaseChannelListItemViewHolder createChannelViewHolder(ChannelListItemAdapter adapter, ViewGroup parent, int viewType) {
        ChannelListViewStyle style = adapter.getStyle();
        // inflate the layout specified in the style
        View v = LayoutInflater.from(parent.getContext()).inflate(style.channelPreviewLayout, parent, false);

        // configure the viewholder
        CustomChannelListItemViewHolder holder = new CustomChannelListItemViewHolder(v);
        configureHolder(holder, adapter);
        return holder;

And a custom view holder which changes how dates are formatted.

package com.example.whatsappclone.ui.channel_list

import android.view.View
import android.widget.TextView
import java.text.SimpleDateFormat

class CustomChannelListItemViewHolder( v : View) : ChannelListItemViewHolder(v) {

    val dateFormat = SimpleDateFormat("MM/dd/YY")

    override fun configLastMessageDate(channelState: ChannelState) {
        val lastMessage = channelState.lastMessage
        val tvDate = itemView.findViewById( as TextView
        if (lastMessage == null) {
            tvDate.text = ""
        if (lastMessage.isToday) tvDate.text = lastMessage.time else tvDate.text =

public class CustomChannelListItemViewHolder extends ChannelListItemViewHolder {

    static SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/YY");

    public CustomChannelListItemViewHolder(@NonNull View itemView) {

    protected void configLastMessageDate(ChannelState channelState) {

        Message lastMessage = channelState.getLastMessage();
        TextView tvDate = itemView.findViewById(;
        if (lastMessage == null) {
        } else if (lastMessage.isToday()) {
        } else {