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 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 CometChat.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.
CometChatMessageOptionDefines “Message Privately” in message context menus.
UserDetailActivityHandles blocked-user UI; hides composer & shows unblock.

Integration Steps

Step 1: Add Thread Layout

Create res/layout/activity_thread_message.xml:
<LinearLayout ...>
  <com.cometchat.ui_kits.ThreadHeaderView
    android:id="@+id/threadHeader"
    ... />
  <com.cometchat.ui_kits.CometChatMessageList
    android:id="@+id/threadMessageList"
    ... />
  <com.cometchat.ui_kits.CometChatMessageComposer
    android:id="@+id/threadComposer"
    ... />
  <View
    android:id="@+id/unblockLayout"
    ... />
</LinearLayout>
File reference:
activity_thread_message.xml

Step 2: Set up ThreadMessageActivity

Initialize UI & handle blocked-user flows:
public class ThreadMessageActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_thread_message);
    ThreadHeaderView header = findViewById(R.id.threadHeader);
    CometChatMessageList messageList = findViewById(R.id.threadMessageList);
    CometChatMessageComposer composer = findViewById(R.id.threadComposer);
    View unblock = findViewById(R.id.unblockLayout);

    String messageId = getIntent().getStringExtra("message_id");
    viewModel = new ViewModelProvider(this).get(ThreadMessageViewModel.class);
    viewModel.fetchParentMessage(messageId);
    viewModel.getParentMessage().observe(this, msg -> header.setMessage(msg));
    messageList.setParentMessage(messageId);
    composer.setParentMessageId(messageId);

    // Handle blocked user
    if (viewModel.isBlocked()) {
      composer.setVisibility(View.GONE);
      unblock.setVisibility(View.VISIBLE);
    }
  }
}
File reference:
ThreadMessageActivity.java

Step 3: Create ThreadMessageViewModel

Fetch parent message and expose LiveData:
public class ThreadMessageViewModel extends ViewModel {
  private MutableLiveData<BaseMessage> parentMessage = new MutableLiveData<>();

  public void fetchParentMessage(String id) {
    CometChat.getMessage(id, new CometChat.CallbackListener<BaseMessage>() {
      @Override
      public void onSuccess(BaseMessage msg) {
        parentMessage.postValue(msg);
      }
      @Override public void onError(CometChatException e) {}
    });
  }

  public LiveData<BaseMessage> getParentMessage() {
    return parentMessage;
  }
}
File reference:
ThreadMessageViewModel.java

Step 4: Hook Thread Entry from Message List

In MessagesActivity.java, capture thread icon taps:
messageList.setOnThreadRepliesClick((context, baseMessage, template) -> {
  Intent intent = new Intent(context, ThreadMessageActivity.class);
  intent.putExtra("message_id", baseMessage.getId());
  context.startActivity(intent);
});
File reference:
MessagesActivity.java

Implementation Flow

  1. User taps thread icon on a message.
  2. Intent launches ThreadMessageActivity with message_id.
  3. ViewModel fetches parent message details.
  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

In UserDetailActivity, detect and toggle UI:
if (user.isBlockedByMe()) {
  composer.setVisibility(View.GONE);
  unblockLayout.setVisibility(View.VISIBLE);
}
File reference:
UserDetailActivity.java

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(messageId)
Show parent messageheader.setMessage(parentMessage)
Compose replycomposer.setParentMessageId(messageId)
Handle blocked usersisBlockedByMe(), hide composer + show unblock UI

Next Steps & Further Reading

Android Sample App (Java)

Explore this feature in the CometChat SampleApp: GitHub → SampleApp

Android Sample App (Kotlin)

Explore this feature in the CometChat SampleApp: GitHub → SampleApp