Skip to main content


The SDK provides an API for general configuration of the UI Component library's behavior and appearance, which is exposed via the ChatUI object.

ChatUI allows you to override default implementations of commonly used parts of SDK like:

  • Available message reactions
  • MIME type icons for attachments
  • Default font used across the UI components
  • Avatar bitmap loading logic
  • Attachments URLs

The full list of ChatUI properties you can override include:

  • supportedReactions: The set of supported message reactions.
  • mimeTypeIconProvider: The icons used for different mime types.
  • fonts: The default font for TextViews displayed by UI Components.
  • avatarBitmapFactory: The factory responsible for creating user and channel avatar bitmaps displayed by AvatarView. Can be used to modify the default bitmaps that are loaded, or to add custom avatar loading logic.
  • imageHeadersProvider: Allows adding extra headers to image loading requests.
  • markdown: Interface to customize the markdown parsing behaviour, useful if you want to provide your custom markdown parsing logic or use more markdown modules. This property is deprecated and ChatUI.messageTextTransformer should be used instead.
  • style: Allows overriding the global, default style of UI components, like the TextStyle.
  • navigator: Allows intercepting and modifying default navigation between SDKs components (e.g. redirection from MessageListView to AttachmentGalleryActivity).
  • messageTextTransformer: Allows customizing the message text's format or style. For example, you can use it to provide markdown support or highlight specific messages by making them bold.
  • channelNameFormatter: Allows customizing the way channel names are formatted.
  • messagePreviewFormatter: Allows to generate a preview text for the given message.
  • dateFormatter: Allows changing the way dates are formatted.
  • showThreadSeparatorInEmptyThread: Allows showing the ThreadSeparatorItem in the MessageListView, when opening an empty thread. This helps implement more complex use cases for empty threads and ThreadPlaceholderItems.

ChatUI is initialized out-of-the-box with default implementations - no initialization is required on app startup.

Custom Reactions#

As shown below, by default the SDK provides 5 built-in reactions.

Default reactions

You can override the default set of reactions. In order to define a custom set of reactions for your app, you need to pass the the following data to the ChatUI.supportedReactions property:

val loveDrawable = ContextCompat.getDrawable(this, R.drawable.ic_reaction_love)!!
val loveDrawableSelected = ContextCompat.getDrawable(this, R.drawable.ic_reaction_love)!!.apply { setTint(Color.RED) }
val supportedReactionsData = mapOf(
"love" to SupportedReactions.ReactionDrawable(loveDrawable, loveDrawableSelected)
ChatUI.supportedReactions = SupportedReactions(this, supportedReactionsData)

As a result, there will only be a love reaction available in the chat, and when selected, the reaction icon will have a red tint.

Normal state - available reactionsActive state - reaction selected

Custom MIME Type Icons#

When possible, the SDK displays thumbnails for image files. For other files, mime type icons are displayed in MessageListView and in the file browser UI.

By default, the SDK provides built-in MIME type icons for the most popular file types and displays a generic file icon for others.

To customize these icons, you need to override ChatUI.mimeTypeIconProvider in the following way:

ChatUI.mimeTypeIconProvider = MimeTypeIconProvider { mimeType ->
when {
// Generic icon for missing MIME type
mimeType == null -> R.drawable.stream_ui_ic_file
// Special icon for XLS files
mimeType == "application/" -> R.drawable.ic_file_xls
// Generic icon for audio files
mimeType.contains("audio") -> R.drawable.ic_file_mp3
// Generic icon for video files
mimeType.contains("video") -> R.drawable.ic_file_mov
// Generic icon for other files
else -> R.drawable.stream_ui_ic_file

Customizing Avatar#

The AvatarView is used in the lists of channels, users, and messages. The avatar is a small picture that identifies a specific channel or user.

The image in the AvatarView is being displayed based on image property, present in both Channel and User objects:

Default avatar

The AvatarView will use default gradient color with initials if the image property cannot be loaded:

Default avatar

Customizing Avatar Using Styles#

You can configure avatar shape, border width, online indicator and other aspects using AvatarStyle. You can create this kind of avatar by changing the shape and border-radius:


You can also change the background and the picture using AvatarBitmapFactory.

Customizing Avatar Using AvatarBitmapFactory#

Overriding the AvatarBitmapFactory allows you to add custom logic for the channel's and user's avatars displayed using AvatarView.

The factory has a pair of methods for both user and channel avatars, such as createUserBitmap and createDefaultUserBitmap. The first should load the avatar from the network, this is attempted first when an avatar is requested. If the first method fails, the Default variant is called as a fallback. This should have an implementation that never fails, and always returns a valid bitmap.


The methods mentioned above are suspending functions. If you don't use coroutines, you'll find variants with Blocking in their name that you can use instead.

If you don't want to use avatars from the internet and only use the default avatar, you can call createDefaultUserBitmap inside createUserBitmap in your custom implementation of AvatarBitmapFactory:

object : AvatarBitmapFactory(context) {
override suspend fun createUserBitmap(user: User, style: AvatarStyle, avatarSize: Int): Bitmap? {
return createDefaultUserBitmap(user, style, avatarSize)

If you only would like to change the gradient colors for the default avatar, you can use stream_ui_avatar_gradient_colors.

The default color set includes a variety of colors:

Colorful avatars

The set can be overridden in the color.xml file - you can expand or reduce the number of supported colors, like in the example below:

<array name="stream_ui_avatar_gradient_colors">

Which creates:

White avatars

If you add custom loading logic for your avatars, you can also override the userBitmapKey and channelBitmapKey methods to get correct caching behavior. The cache keys returned from these methods should include a representation of the pieces of data you use to load the avatars.

Here's an example of a custom factory that extends AvatarBitmapFactory, which applies a grayscale transformation to the avatars of users who are currently offline:

ChatUI.avatarBitmapFactory = object : AvatarBitmapFactory(context) {
override suspend fun createUserBitmap(user: User, style: AvatarStyle, avatarSize: Int): Bitmap? {
val imageResult = context.imageLoader.execute(
.apply {
if (! {

return (imageResult.drawable as? BitmapDrawable)?.bitmap

This example uses the Coil library for loading the image URL and applying transformations to it. If you use Coil version 2.x, you should copy-paste GrayscaleTransformation.kt manually in your project for building this example.

This results in avatars rendered like this:

Shades of gray images for offline users

Excluding and Including the Current User in ChannelAvatars#

The default setting for our ChannelAvatars is to exclude the current user's image from the bitmap matrix.

Group Avatar Current User Excluded

If you want to disable this setting, you can do so by overriding a resource in your app, like so:

<bool name="stream_ui_excludeCurrentUserFromChannelAvatars">false</bool>

By passing false to stream_ui_excludeCurrentUserFromChannelAvatars, you'll stop the current user from being excluded in the list of last active users in a channel, when displaying the Channel members bitmap.

Group Avatar Current User Excluded

Adding Extra Headers to Image Requests#

If you're using your own CDN, you might also need to add extra headers to image loading requests. You can do this by creating your own implementation of the ImageHeadersProvider interface and then setting it on ChatUI:

ChatUI.imageHeadersProvider = object : ImageHeadersProvider {
override fun getImageRequestHeaders(): Map<String, String> {
return mapOf("token" to "12345")

Changing the Default Font#

You can customize the default fonts used by all of the UI components. To change the fonts, implement the ChatFont interface and set the new implementation on ChatUI:

ChatUI.fonts = object : ChatFonts {
override fun setFont(textStyle: TextStyle, textView: TextView) {

override fun setFont(textStyle: TextStyle, textView: TextView, defaultTypeface: Typeface) {

override fun getFont(textStyle: TextStyle): Typeface? = textStyle.font

Transforming Message Text#

You can easily provide a transformer that can transform and apply the message text to a given TextView. You need to override ChatUI.messageTextTransformer to an instance of ChatMessageTextTransformers implementation.

ChatUI.messageTextTransformer = ChatMessageTextTransformer { textView: TextView, messageItem: MessageItem ->
// Transform messages to upper case.
textView.text = messageItem.message.text.uppercase()

Stream UI TextView components don't have android:autoLink property set because it conflicts with Markdown plugins.


You can use AutoLinkableTextTransformer if you want to apply custom transformation but keep links clickable.


The SDK provides a standalone Markdown module stream-chat-android-markdown-transformer that contains MarkdownTextTransformer which is an implementation of ChatMessageTextTransformer. It uses Markwon library internally.

ChatUI.messageTextTransformer = MarkdownTextTransformer(context)

If you use MarkdownTextTransformer, don't use android:autoLink attribute because it'll break the markdown Linkify implementation.

Then the SDK will parse Markdown automatically:

Markdown messages


The SDK performs navigation in certain cases:

  • Navigating to AttachmentGalleryActivity to display the gallery of pictures in the chat.
  • Opening the browser after clicking a link in the chat.

This is done performed by ChatNavigator, which you can provide a custom ChatNavigationHandler implementation to override its behavior:

val navigationHandler = ChatNavigationHandler { destination: ChatDestination ->
// Perform some custom action here

ChatUI.navigator = ChatNavigator(navigationHandler)

Customizing ChannelNameFormatter#

You can customize the way channel names are formatted by overriding the default ChannelNameFormatter:

ChatUI.channelNameFormatter = ChannelNameFormatter { channel, currentUser ->

Customizing MessagePreviewFormatter#

You can change the way the last messages are formatted in the channel list by overriding the default MessagePreviewFormatter:

ChatUI.messagePreviewFormatter = MessagePreviewFormatter { channel, message, currentUser ->

Customizing DateFormatter#

Overriding the DateFormatter allows you to change the way dates are formatted in the application:

ChatUI.dateFormatter = object: DateFormatter {
private val dateFormatter = DateTimeFormatter.ofPattern("yy MM dd")
private val dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")

override fun formatDate(localDateTime: LocalDateTime?): String {
localDateTime ?: return ""
return dateFormatter.format(localDateTime)

override fun formatTime(localTime: LocalTime?): String {
localTime ?: return ""
return dateTimeFormatter.format(localTime)

Showing ThreadSeparatorItem in empty threads#

Overriding the showThreadSeparatorInEmptyThread allows you to show the separator in empty threads:

ChatUI.showThreadSeparatorInEmptyThread = true

Using this feature, you get the following behavior in empty threads.

Empty thread date separator

On its own, it might not look like much, but when customizing the ThreadPlaceholderItem, you can achieve powerful expanded behavior of threads, where you denote to the user that they're in an empty thread where they can leave replies.

Did you find this page helpful?