Messages in Stream Chat can contain a number of attachments. The UI Components library for Android renders these by default depending on their type. Images are rendered in a gallery layout, files are shown in a list, and links show rich previews of the content they're leading to.
You can render attachments in a custom way inside the same area within the message bubble. Here's the area filled with a custom attachment that's just a plain red box:
To determine when to do this, you can inspect the contents of each message item in the list, and decide whether you want to override the default attachments rendering for a given message.
Customizing attachments is the simpler of the two main customization options in the message list. The more complex approach is rendering an entire message by yourself - which is also possible with other APIs in the Stream Chat Android SDK. Those give you even more control, but you'll lose all of the default message UI (timestamps, avatars, read statuses, thread indicators, etc.) and have to build the entire message view from scratch.
Examples of three different message items in a list with various decoration
AttachmentViewFactory
Implementing custom attachments is done by extending the AttachmentViewFactory
class from the SDK and overriding its createAttachmentView
method. In general, you'll likely follow this kind of pattern for a custom attachment there:
1234567891011121314class CustomAttachmentViewFactory : AttachmentViewFactory() { override fun createAttachmentView( data: MessageListItem.MessageItem, listeners: MessageListListenerContainer, style: MessageListItemStyle, parent: ViewGroup, ): View { return if (someCondition) { createCustomView() } else { super.createAttachmentView(data, listeners, style, parent) } } }
You'll check some conditions based on the data
received as a parameter, and use your own View creation logic when the condition is met. Otherwise, you can call the super
method which will use the default rendering logic of the SDK.
You receive the entire Message
object in data
, so you could write code that handles multiple (or all) attachments on a given message. You could also add contents to the attachments area based on just the Message
contents alone, possibly ignoring the attachments on it altogether.
When your factory is implemented, you can set it on MessageListView
so that it will use your custom logic for rendering attachments, like so:
1messageListView.setAttachmentViewFactory(CustomAttachmentViewFactory())
Custom password attachment UI
For a real life use case, we'll create a special "password"
attachment type which we can use for sending passwords in our messages. We won't deal with encryption this time around, but we'll make sure that the contents of the password attachment are hidden on the UI by default, to keep it safe from prying eyes.
First, let's create a layout file called attachment_password.xml
:
12345678910111213141516171819<?xml version="1.0" encoding="utf-8"?> <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" app:cardBackgroundColor="#FF4D56" app:cardCornerRadius="12dp"> <TextView android:id="@+id/passwordText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:inputType="textPassword" android:textSize="30sp" /> </com.google.android.material.card.MaterialCardView>
We'll use a MaterialCardView
to create UI with rounded corners, matching the default styling of the Chat UX Kit. Inside that, we're adding a TextView
to display the password, with the textPassword
input type set by default, which hides the text content inside it.
Time to implement the factory to produce this layout when needed:
12345678910111213141516171819202122232425262728293031323334353637class CustomAttachmentViewFactory : AttachmentViewFactory() { override fun createAttachmentView( data: MessageListItem.MessageItem, listeners: MessageListListenerContainer, style: MessageListItemStyle, parent: ViewGroup, ): View { // 1 val passwordAttachment = data.message.attachments.find { it.type == "password" } return if (passwordAttachment != null) { // 2 createPasswordView(parent, passwordAttachment.extraData.get("password") as String) } else { // 3 super.createAttachmentView(data, listeners, style, parent) } } private fun createPasswordView(parent: ViewGroup, password: String): View { // 4 val binding = AttachmentPasswordBinding .inflate(LayoutInflater.from(parent.context), parent, false) // 5 binding.passwordText.text = password binding.passwordText.setOnClickListener { binding.passwordText.inputType = binding.passwordText.inputType xor TYPE_TEXT_VARIATION_PASSWORD xor TYPE_TEXT_VARIATION_VISIBLE_PASSWORD } return binding.root } }
Let's review this code step-by-step:
- We're going through the attachments in the current message to determine whether it has an attachment with the
"password"
type. (Again, we could also handle all attachments on theMessage
if needed. We're making some assumptions about the structure of the message object here.) - We're calling into our own method that will create the password UI.
- If we didn't find an attachment with our custom type, we fall back to the default implementation that will render link previews, media, and files.
- In our custom method, we inflate the XML layout using ViewBinding.
- We then populate this layout with data, and set up a listener so that clicking the password field will toggle between the regular and the visible password input types, hiding and showing the password.
Creating and sending custom attachments
All we have left to do is to send an attachment that contains a password to see our code in action. For this, we can use the following code to create an Attachment
and add it to a Message
. We're using the extraData
map to add arbitrary info to the attachment, in this case, a password
field that will contain the value we want to send.
12345678910val attachment = Attachment( type = "password", extraData = mutableMapOf("password" to "12345"), ) val message = Message( cid = cid, text = "This is the combination on my luggage", attachments = mutableListOf(attachment), ) ChatDomain.instance().useCases.sendMessage(message).enqueue(...)
The example above uses ChatDomain
to send the constructed message, a class from our offline support library. Using this is the recommended way of sending messages if you're using offline support.
There are other ways of sending messages with the SDK, for example, by using ChatClient
directly. Read more about Sending Messages in the documentation.
Custom attachments in action
Sending a custom attachment as described above will create a message like this on your UI now:
With the listener set up, you can tap on this View to toggle the visibility of the password:
Conclusion
That's all it takes to send and render custom attachments with Stream Chat's Android SDK! You can use this to easily add custom media, a map, product details, or whatever other content your app deals with to a message.
You can learn more about in-app messaging and the Android SDK by checking out its GitHub repository (give it a ⭐️ while you're there), and by taking a look at the documentation. You can also go through the Android tutorial that shows you how to get started with the UI SDK.
Thanks for reading this article! Find us on Twitter @getstream_io or drop us feedback on GitHub if you have any questions.