Message List View

LAST EDIT Apr 12 2021

MessageListView renders a list of messages. It implements all the features you expect from a chat:

  • Displaying text, images, and attachments

  • Rich URL previews

  • Editing, copying, flagging, and deleting messages

  • Opening and creating threads

  • Reactions

  • Read state

  • Replies

  • Muting and blocking users

  • Custom attachments

MessageListView in light mode

MessageListView, like our other components, supports dark mode:

MessageListView in dark mode

The example below shows how to use the MessageListView in your layout:

1
2
3
4
<io.getstream.chat.android.ui.message.list.MessageListView 
    android:id="@+id/messageList" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" />

Displaying new messages

Copied!

To display new messages in the view, use displayNewMessages:

1
2
3
4
5
6
7
8
val messageItem = MessageListItem.MessageItem( 
    message = Message(text = "Lorem ipsum dolor"), 
    positions = listOf(MessageListItem.Position.TOP), 
    isMine = true 
) 
 
val messageItemListWrapper = MessageListItemWrapper(listOf(messageItem)) 
binding.messageListView.displayNewMessages(messageItemListWrapper)
1
2
3
4
5
6
7
8
9
10
11
Message message = new Message(); 
message.setText("Lorem ipsum dolor"); 
MessageListItem.MessageItem messageItem = new MessageListItem.MessageItem( 
    message, new ArrayList<>(), true, new ArrayList<>(), false, false 
); 
 
MessageListItemWrapper messageWrapper = new MessageListItemWrapper( 
    Collections.singletonList(messageItem), false, false, false 
); 
 
binding.messageListView.displayNewMessages(messageWrapper);

This piece of code will show a simple message in the chat. One way to avoid handling the logic of getting new messages and calling displayNewMessages manually, it is possible to use the MessageListViewModel, as described in the next section.

Showing empty state

Copied!

Show empty state:

1
2
3
fun emptyState() {         
    messageListView.showEmptyStateView() 
}
1
2
3
public void emptyState() { 
    messageListView.showEmptyStateView(); 
}

Showing loading state

Copied!

Show loading state:

1
messageListView.showLoadingView()
1
2
//When loading information, show loading view 
messageListView.showLoadingView();

Binding with a ViewModel

Copied!

To handle the implementation for receiving new messages, showing loading state, pagination... and many other common behaviours, it is recommended to use MessageListViewModel. The following code shows how to connect the view and the ViewModel using bindView:

1
2
3
4
5
// Get ViewModel 
val factory: MessageListViewModelFactory = MessageListViewModelFactory(cid = "channelType:channelId") 
val viewModel: MessageInputViewModel by viewModels { factory } 
// Bind it with MessageInputView 
viewModel.bindView(messageInputView, viewLifecycleOwner)
1
2
3
4
5
6
7
// Get ViewModel 
MessageListViewModelFactory factory = new MessageListViewModelFactory("channelType:channelId"); 
MessageListViewModel viewModel = 
        new ViewModelProvider(this, factory).get(MessageListViewModel.class); 
 
// Bind it with MessageListView 
MessageListViewModelBinding.bind(viewModel, messageListView, getViewLifecycleOwner());

This will set the listeners and handlers on the message list, forwarding everything to the ViewModel. It is also possible to set these manually: the options for listeners and handlers are described in the next sections.

Listeners

Copied!

MessageListView provides multiple methods for handling user interactions handling, that includes different clicks:

1
2
3
4
5
6
7
8
9
messageListView.setMessageClickListener { message -> 
    // Handle click on message 
} 
messageListView.setMessageLongClickListener { message -> 
    // Handle long click on message 
} 
messageListView.setAttachmentClickListener { message, attachment -> 
    // Handle long click on attachment 
}
1
2
3
4
5
6
7
8
9
messageListView.setMessageClickListener((message) -> { 
    // Handle click on message 
}); 
messageListView.setMessageLongClickListener((message) -> { 
    // Handle long click on message 
}); 
messageListView.setAttachmentClickListener((message, attachment) -> { 
    // Handle click on attachment 
});

A full list of available listeners to set can be found here.

Handlers

Copied!

It is also possible to change general behaviour by using handlers. For example:

1
2
3
4
5
6
7
8
9
messageListView.setMessageEditHandler { message -> 
    // Handle edit message 
} 
messageListView.setMessageDeleteHandler { message -> 
    // Handle delete message 
} 
messageListView.setAttachmentDownloadHandler { attachment -> 
    // Handle attachment download 
}
1
2
3
4
5
6
7
8
9
messageListView.setMessageEditHandler((message) -> { 
    // Handle edit message 
}); 
messageListView.setMessageDeleteHandler((message) -> { 
    // Handle delete message 
}); 
messageListView.setAttachmentDownloadHandler((attachment) -> { 
    // Handle attachment download 
});

The full list of available handlers is available here.

Customizations

Copied!

Many properties about this view, like colours, padding and margins, can be changed via XML, the list of attributes can be found here.

Some other customizations can be done programmatically. Like:

Using Transform Style

Copied!

The view and its view holders styles can be configured programmatically using TransformStyle.messageListStyleTransformer and TransformStyle.messageListItemStyleTransformer (a list of supported customizations can be found here and here):

1
2
3
4
5
6
7
8
9
TransformStyle.messageListStyleTransformer = StyleTransformer { defaultMessageListViewStyle -> 
    // Modify default MessageListView style 
    defaultMessageListViewStyle.copy() 
} 
 
TransformStyle.messageListItemStyleTransformer = StyleTransformer { defaultMessageListItemStyle -> 
    // Modify default MessageListItem style 
    defaultMessageListItemStyle.copy() 
}
1
2
3
4
5
6
7
8
9
TransformStyle.INSTANCE.setMessageListStyleTransformer(defaultMessageListViewStyle -> { 
    // Modify default MessageListView style 
    return defaultMessageListViewStyle; 
}); 
             
TransformStyle.INSTANCE.setMessageListItemStyleTransformer(defaultMessageListItemStyle -> { 
    // Modify default MessageListItem style 
    return defaultMessageListItemStyle; 
});

NOTE: The style transformer should be set before the view is rendered to make sure that the new style was applied.

New Messages Behaviour

Copied!

It is possible to have 2 behaviours for when new messages arrive, scroll to the bottom of the screen when a new message arrives (SCROLL_TO_BOTTOM), or count the number of new messages that arrived (COUNT_UPDATE). They can be set using the XML, as described in the section above, or using setNewMessagesBehaviour:

1
2
3
fun setNewMessageBehaviour() { 
    messageListView.setNewMessagesBehaviour(MessageListView.NewMessagesBehaviour.COUNT_UPDATE) 
}
1
2
3
4
5
public void setNewMessageBehaviour() { 
    messageListView.setNewMessagesBehaviour( 
        MessageListView.NewMessagesBehaviour.COUNT_UPDATE 
    ); 
}

Date time formatter

Copied!

It is necessary to implement the interface com.getstream.sdk.chat.utils.DateFormatter:

1
2
3
4
5
6
7
8
9
10
11
12
13
messageListView.setMessageDateFormatter( 
    object : DateFormatter { 
        override fun formatDate(localDateTime: LocalDateTime?): String { 
            // Provide a way to format Date 
            return DateTimeFormatter.ofPattern("dd/MM/yyyy").format(localDateTime) 
        } 
 
        override fun formatTime(localTime: LocalTime?): String { 
            // Provide a way to format Time. 
           return DateTimeFormatter.ofPattern("HH:mm").format(localTime) 
       } 
   } 
)
1
2
3
4
5
6
7
8
9
10
11
12
13
messageListView.setMessageDateFormatter(new DateFormatter() { 
    @NotNull 
    @Override 
    public String formatDate(@Nullable LocalDateTime localDateTime) { 
        return DateTimeFormatter.ofPattern("dd/MM/yyyy").format(localDateTime); 
    } 
 
    @NotNull 
    @Override 
   public String formatTime(@Nullable LocalTime localTime) { 
       return DateTimeFormatter.ofPattern("HH:mm").format(localTime); 
   } 
});

Result:

Format of HH:mm

MessageListItemViewHolderFactory

Copied!

To change the way messages are presented to the user, it is necessary to set the MessageListItemViewHolderFactory, which will provide a different kind of messages accordingly to the message type:

1
2
3
4
5
6
7
8
9
10
11
12
class MessageListItemViewHolderFactoryExtended : MessageListItemViewHolderFactory() { 
    override fun createViewHolder( 
        parentView: ViewGroup, 
        viewType: Int, 
    ): BaseMessageItemViewHolder<out MessageListItem> { 
        // Create a new type of view holder here, if needed 
        return super.createViewHolder(parentView, viewType) 
    } 
} 
 
val factory: MessageListItemViewHolderFactory = MessageListItemViewHolderFactoryExtended() 
messageListView.setMessageViewHolderFactory(factory)
1
2
3
4
5
6
7
8
9
10
11
class MessageListItemViewHolderFactoryExtended extends MessageListItemViewHolderFactory { 
    @NotNull 
    @Override 
    public BaseMessageItemViewHolder<? extends MessageListItem> createViewHolder(@NotNull ViewGroup parentView, int viewType) { 
        // Create a new type of view holder here, if needed 
        return super.createViewHolder(parentView, viewType); 
    } 
} 
 
MessageListItemViewHolderFactory factory = new MessageListItemViewHolderFactoryExtended(); 
messageListView.setMessageViewHolderFactory(factory);

Filter messages

Copied!

It is possible to filter messages. The predicate must be set using setMessageListItemPredicate, and the messages will be filtered once they arrive:

1
2
3
4
messageListView.setMessageListItemPredicate { messageList -> 
    // Boolean logic here 
    true 
}
1
2
3
4
messageListView.setMessageListItemPredicate(messageList -> { 
    // Boolean logic here 
    return true; 
});

Changing behaviour for end region reached

Copied!

It is possible to set a listener for when the end region was reached:

1
2
3
4
5
6
7
messageListView.setEndRegionReachedHandler { 
    // Handle pagination and include new logic 
 
    // Option to log the event and use the viewModel 
    viewModel.onEvent(MessageListViewModel.Event.EndRegionReached) 
    Log.e("LogTag", "On load more") 
}
1
2
3
4
5
6
7
messageListView.setEndRegionReachedHandler(() -> { 
    // Handle pagination and include new logic 
 
    // Option to log the event and use the viewModel 
    viewModel.onEvent(MessageListViewModel.Event.EndRegionReached.INSTANCE); 
    Log.e("LogTag", "On load more"); 
});

NOTE: Please be aware that if you're using the ViewModel and override the EndRegionReachedHandler you have to handle pagination, otherwise pagination won't work.

Custom attachments

Copied!

By default, the message list view can show a message with text and attachments. The kind of attachments can be:

  • Media attachments (pictures, gifs and so on)

  • File attachments (different non-media files)

  • Some links (previews - youtube as an example)

  • Combinations of media/files + links attachments

Attachments content is placed above the text of a message. If you'd like to draw any of these types in the custom way you're able to do this by using custom AttachmentViewFactory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private class CustomAttachmentViewFactory extends AttachmentViewFactory { 
    @NotNull 
    @Override 
    public View createAttachmentView( 
            @NotNull MessageListItem.MessageItem data, 
            @NotNull MessageListListenerContainer listeners, 
            @NotNull MessageListItemStyle style, 
            @NotNull ViewGroup parent 
    ) { 
        return super.createAttachmentView(data, listeners, style, parent); 
    } 
} 
 
messageListView.setAttachmentViewFactory(new CustomAttachmentViewFactory());
1
2
3
4
5
6
7
8
9
10
11
12
class CustomAttachmentViewFactory : AttachmentViewFactory() { 
    override fun createAttachmentView( 
        data: MessageListItem.MessageItem, 
        listeners: MessageListListenerContainer, 
        style: MessageListItemStyle, 
        parent: ViewGroup 
    ): View { 
        return super.createAttachmentView(data, listeners, style, parent) 
    } 
} 
 
messageListView.setAttachmentViewFactory(CustomAttachmentViewFactory())

This factory lets you override content for attachments in the message list. For example, if you want to customize only your particular attachments and keep default behaviour for other attachments you can do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyAttachmentViewFactory extends AttachmentViewFactory { 
 
    private static final String MY_URL_ADDRESS = "https://myurl.com"; 
 
    @NotNull 
    @Override 
    public View createAttachmentView( 
            @NotNull MessageListItem.MessageItem data, 
            @NotNull MessageListListenerContainer listeners, 
            @NotNull MessageListItemStyle style, 
            @NotNull ViewGroup parent 
    ) { 
        boolean containsMyAttachments = false; 
        for (Attachment attachment: data.getMessage().getAttachments()) { 
            if (attachment.getImageUrl().contains(MY_URL_ADDRESS)) { 
                containsMyAttachments = true; 
            } 
        } 
 
        if (containsMyAttachments) { 
            // put your custom attachment view creation here 
            return new View(parent.getContext()); 
        } else { 
            return super.createAttachmentView(data, listeners, style, parent); 
        } 
    } 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyAttachmentViewFactory : AttachmentViewFactory() { 
    private val MY_URL_ADDRESS = "https://myurl.com" 
 
    override fun createAttachmentView( 
        data: MessageListItem.MessageItem, 
        listeners: MessageListListenerContainer, 
        style: MessageListItemStyle, 
        parent: ViewGroup, 
    ): View { 
        return if (data.message.attachments.any { it.imageUrl?.contains(MY_URL_ADDRESS) == true }) { 
            // put your custom attachment view creation here 
            View(parent.context) 
        } else { 
            super.createAttachmentView(data, listeners, style, parent) 
        } 
    } 
}