Skip to main content
FieldValue
Packagescom.cometchat:chatuikit-kotlin · com.cometchat:chatuikit-jetpack
Key componentsCometChatThreadHeader, CometChatMessageList, CometChatMessageComposer
PurposeImplement threaded replies so users can respond to specific messages in a focused sub-conversation.
RelatedThreaded Messages Header, Message List, Message Composer, All Guides
Implement threaded replies in your Android chat app using CometChat’s UI Kit, enabling users to reply to specific messages in a focused sub-conversation.

Overview

Threaded replies allow users to respond directly to a specific message in one-on-one or group chats, improving context and readability:
  • Organizes related replies into a dedicated thread view.
  • Mirrors functionality in Slack, Discord, and WhatsApp.
  • Maintains clarity in active conversations.
Users tap a message → open thread screen → view parent message + replies → compose within thread.

Prerequisites

  • Android project in Android Studio.
  • CometChat Android UI Kit v5 (com.cometchat:chatuikit-kotlin or com.cometchat:chatuikit-jetpack) added to your build.gradle.
  • Valid CometChat App ID, Auth Key, and Region initialized.
  • <uses-permission android:name="android.permission.INTERNET"/> in AndroidManifest.xml.
  • Logged-in user via CometChatUIKit.login().
  • Existing MessagesActivity using CometChatMessageList.

Components

ComponentRole
activity_thread_message.xmlDefines thread UI: header, message list, composer, unblock.
ThreadMessageActivityHosts thread screen; initializes UI & ViewModel.
ThreadMessageViewModelFetches parent message & thread replies; manages state.
CometChatMessageListDisplays threaded replies when given parent message ID.
CometChatMessageComposerComposes and sends replies with parentMessageId.
CometChatThreadHeaderDisplays the parent message context at the top of the thread.

Integration Steps

Step 1: Add Thread Layout

Create res/layout/activity_thread_message.xml:
activity_thread_message.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.cometchat.uikit.kotlin.presentation.threadheader.ui.CometChatThreadHeader
        android:id="@+id/threadHeader"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <com.cometchat.uikit.kotlin.presentation.messagelist.ui.CometChatMessageList
        android:id="@+id/threadMessageList"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:id="@+id/unblockLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="12dp"
        android:visibility="gone">

        <Button
            android:id="@+id/unblockBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Unblock" />
    </LinearLayout>

    <com.cometchat.uikit.kotlin.presentation.messagecomposer.ui.CometChatMessageComposer
        android:id="@+id/threadComposer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Step 2: Set up ThreadMessageActivity

Initialize UI & handle blocked-user flows:
import com.cometchat.uikit.kotlin.presentation.threadheader.ui.CometChatThreadHeader
import com.cometchat.uikit.kotlin.presentation.messagelist.ui.CometChatMessageList
import com.cometchat.uikit.kotlin.presentation.messagecomposer.ui.CometChatMessageComposer

class ThreadMessageActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_thread_message)
        val header = findViewById<CometChatThreadHeader>(R.id.threadHeader)
        val messageList = findViewById<CometChatMessageList>(R.id.threadMessageList)
        val composer = findViewById<CometChatMessageComposer>(R.id.threadComposer)
        val unblock = findViewById<View>(R.id.unblockLayout)

        val rawMessage = intent.getStringExtra("raw_json")
        val viewModel = ViewModelProvider(this)[ThreadMessageViewModel::class.java]
        if (rawMessage != null) {
            val parentMessage = BaseMessage.processMessage(JSONObject(rawMessage))
            viewModel.setParentMessage(parentMessage)
        }

        lifecycleScope.launch {
            viewModel.parentMessage.collect { msg ->
                msg?.let {
                    header.setParentMessage(it)
                    messageList.setParentMessage(it.id)
                    composer.setParentMessageId(it.id)
                }
            }
        }

        // Handle blocked user
        if (isBlockedByMe) {
            composer.visibility = View.GONE
            unblock.visibility = View.VISIBLE
        }
    }
}

Step 3: Create ThreadMessageViewModel

Store parent message and expose via StateFlow:
class ThreadMessageViewModel : ViewModel() {
    private val _parentMessage = MutableStateFlow<BaseMessage?>(null)
    val parentMessage: StateFlow<BaseMessage?> = _parentMessage.asStateFlow()
    var id: Long = 0
        private set

    fun setParentMessage(message: BaseMessage?) {
        if (message != null) {
            id = message.id
            _parentMessage.value = message
        }
    }
}

Step 4: Hook Thread Entry from Message List

In your MessagesActivity, capture thread icon taps:
messageList.setOnThreadRepliesClick { context, baseMessage, template ->
    val intent = Intent(context, ThreadMessageActivity::class.java)
    intent.putExtra("raw_json", baseMessage.rawMessage.toString())
    intent.putExtra("reply_count", baseMessage.replyCount)
    context.startActivity(intent)
}

Implementation Flow

  1. User taps thread icon on a message.
  2. Intent launches ThreadMessageActivity with raw message JSON (or Compose navigation).
  3. ViewModel stores parent message and exposes via StateFlow.
  4. Header & MessageList render parent + replies.
  5. Composer sends new replies under the parent message.
  6. Live updates flow automatically via UI Kit.

Customization Options

  • Styling: Override theme attributes or call setter methods on views.
  • Header Height: threadHeader.setMaxHeight(...).
  • Hide Reactions: threadHeader.setReactionVisibility(View.GONE).

Filtering & Edge Cases

  • Group Membership: Verify membership before enabling composer.
  • Empty Thread: Show placeholder if no replies.
  • Blocked Users: Composer hidden; use unblock layout.

Blocked-User Handling

if (user.isBlockedByMe) {
    composer.visibility = View.GONE
    unblockLayout.visibility = View.VISIBLE
}

Group vs. User-Level Differences

ScenarioBehavior
ReceiverType.USERDirect replies allowed if not blocked.
ReceiverType.GROUPChecks membership before thread access.
Blocked UserComposer hidden; unblock layout shown.
Not in GroupShow option to join group first.

Summary / Feature Matrix

FeatureComponent / Method
Show thread optionsetOnThreadRepliesClick()
Display thread messagesmessageList.setParentMessage(parentMessage.getId())
Show parent messageheader.setParentMessage(parentMessage)
Compose replycomposer.setParentMessageId(parentMessage.getId())
Handle blocked usersisBlockedByMe(), hide composer + show unblock UI

Next Steps & Further Reading

Android Sample App (Kotlin)

Explore this feature in the CometChat SampleApp: GitHub → SampleApp