View Slots let you swap out specific parts of a component’s list item — the avatar area, title, subtitle, trailing section, or the entire row — while keeping the rest of the component’s behavior intact.
The ViewHolderListener Pattern
Every list-based component defines a ViewHolderListener abstract class (e.g., ConversationsViewHolderListener) with two callbacks:
| Callback | Purpose |
|---|
createView(Context context, CometchatConversationsListItemsBinding listItem) | Return a View that will be placed in the slot. Called once when the ViewHolder is created. |
bindView(Context context, View createdView, Conversation conversation, RecyclerView.ViewHolder holder, List<Conversation> conversationList, int position) | Bind data to your custom view. Called every time the item is bound (scroll, update). createdView is the view returned by createView, and the full conversation list and holder are provided for context. |
Available View Slots
| Setter | Region | Description |
|---|
setLeadingView | Left section | Replaces the avatar / leading area of each list item. |
setTitleView | Title text | Replaces the name / title text area. |
setSubtitleView | Subtitle text | Replaces the last message preview area. |
setTrailingView | Right section | Replaces the timestamp / badge / trailing area. |
setItemView | Entire row | Replaces the entire list item layout. |
Slot-Level vs Full Item Replacement
Use slot-level setters (setLeadingView, setTitleView, setSubtitleView, setTrailingView) when you want to customize one region while keeping the default layout for everything else. Use setItemView when you need complete control over the entire row layout.
When you use setItemView, all other slot setters are ignored since the entire row is replaced.
Example: Custom Leading View
Replace the default avatar with a custom view that shows the first letter of the conversation name in a colored circle:
conversations.setLeadingView(object : ConversationsViewHolderListener() {
override fun createView(
context: Context,
listItem: CometchatConversationsListItemsBinding
): View {
val textView = TextView(context).apply {
layoutParams = ViewGroup.LayoutParams(48.dp, 48.dp)
gravity = Gravity.CENTER
textSize = 18f
setTextColor(Color.WHITE)
}
return textView
}
override fun bindView(
context: Context,
createdView: View,
conversation: Conversation,
holder: RecyclerView.ViewHolder,
conversationList: List<Conversation>,
position: Int
) {
val textView = createdView as TextView
val name = conversation.conversationWith?.name ?: ""
textView.text = name.firstOrNull()?.uppercase() ?: "?"
textView.background = GradientDrawable().apply {
shape = GradientDrawable.OVAL
setColor(Color.parseColor("#6851D6"))
}
}
})
conversations.setLeadingView(new ConversationsViewHolderListener() {
@Override
public View createView(Context context, CometchatConversationsListItemsBinding listItem) {
TextView textView = new TextView(context);
textView.setLayoutParams(new ViewGroup.LayoutParams(48, 48));
textView.setGravity(Gravity.CENTER);
textView.setTextSize(18f);
textView.setTextColor(Color.WHITE);
return textView;
}
@Override
public void bindView(Context context, View createdView, Conversation conversation,
RecyclerView.ViewHolder holder, List<Conversation> conversationList, int position) {
TextView textView = (TextView) createdView;
String name = conversation.getConversationWith() != null
? conversation.getConversationWith().getName() : "";
String initial = name.isEmpty() ? "?" : String.valueOf(name.charAt(0)).toUpperCase();
textView.setText(initial);
GradientDrawable bg = new GradientDrawable();
bg.setShape(GradientDrawable.OVAL);
bg.setColor(Color.parseColor("#6851D6"));
textView.setBackground(bg);
}
});
Example: Custom Subtitle View
Show a “typing…” indicator or a custom last message format in the subtitle area:
conversations.setSubtitleView(object : ConversationsViewHolderListener() {
override fun createView(
context: Context,
listItem: CometchatConversationsListItemsBinding
): View {
return TextView(context).apply {
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
}
}
override fun bindView(
context: Context,
createdView: View,
conversation: Conversation,
holder: RecyclerView.ViewHolder,
conversationList: List<Conversation>,
position: Int
) {
val textView = createdView as TextView
val lastMessage = conversation.lastMessage
textView.text = when (lastMessage) {
is TextMessage -> lastMessage.text
is MediaMessage -> "📎 ${lastMessage.attachment?.fileExtension ?: "Media"}"
else -> "New conversation"
}
}
})
conversations.setSubtitleView(new ConversationsViewHolderListener() {
@Override
public View createView(Context context, CometchatConversationsListItemsBinding listItem) {
TextView textView = new TextView(context);
textView.setMaxLines(1);
textView.setEllipsize(TextUtils.TruncateAt.END);
return textView;
}
@Override
public void bindView(Context context, View createdView, Conversation conversation,
RecyclerView.ViewHolder holder, List<Conversation> conversationList, int position) {
TextView textView = (TextView) createdView;
BaseMessage lastMessage = conversation.getLastMessage();
String subtitle;
if (lastMessage instanceof TextMessage) {
subtitle = ((TextMessage) lastMessage).getText();
} else if (lastMessage instanceof MediaMessage) {
subtitle = "Media";
} else {
subtitle = "New conversation";
}
textView.setText(subtitle);
}
});
Use setOverflowMenu(View) to inject a custom view into the component’s toolbar area. This is separate from the list item view slots — it customizes the toolbar, not individual rows.
val menuButton = ImageButton(context).apply {
setImageResource(R.drawable.ic_filter)
setOnClickListener { /* show filter dialog */ }
}
conversations.setOverflowMenu(menuButton)
ImageButton menuButton = new ImageButton(context);
menuButton.setImageResource(R.drawable.ic_filter);
menuButton.setOnClickListener(v -> { /* show filter dialog */ });
conversations.setOverflowMenu(menuButton);