cometchat-android-v6-kotlin-placement

v2026.09.25

Where the CometChat Android v6 chat UI goes in a Kotlin XML Views app — the default conversations→message Activity flow, growing to the tab-based app (chats/users/groups/calls + detail, thread and search screens + incoming calls), a single one-to-one screen, or chat embedded in an existing Activity/Fragment, with a correct back stack. Triggers: 'tab based chat android', 'add a chat tab to my app', 'open a chat screen for this user', 'embed cometchat in my activity', 'full chat app android kotlin'.

GitHub
Install command
npx skhub add cometchat/cometchat-android-v6-kotlin-placement
Markdown
SKILL.md

Ground truth: catalog android-v6.json + contracts.android-v6.json (core-chat-surface, chat-experience) + the live recipes (conversation-message-view, tab-based-chat, one-to-one-chat). Signatures below were verified against the INSTALLED 6.0.5 source. Fetch anything else from docs (core → references/docs-map.md).

⚠️ Kotlin XML Views cohort. Screens are Activities/Fragments hosting kit Views. The Compose cohort (NavHost + composables) is a separate skill.

Companion skills (read first)

  • cometchat-android-v6-core — install, credentials, initFromSettings → login → render, and references/layout.md (the ONE sizing standard). Sizing rules live THERE; this skill places screens.
  • cometchat-android-v6-kotlin-components — what exists in this cohort.

Use this skill when

"put chat behind a tab", "build the full chat app", "open a chat screen for this user/group", "embed the conversation list in an existing screen", "how should I navigate between the list and the messages".

Prerequisites & install

Same kit + initFromSettings → login gate as core — no chat Activity is started before login resolves. Every Activity below must be lifecycle-aware (AppCompatActivity/ComponentActivity/FragmentActivity) — the kit hooks lifecycle events — and registered in AndroidManifest.xml.

The rule that governs every layout: one screen per Activity

Android composes by navigation, not by side-by-side panes. The list, the message screen, the thread and search are separate destinations with a real back stack. Never build a web-style two-pane split or a "side panel" on a phone.

Placement 1 — conversations → messages (the default)

The core-chat-surface contract. core carries the full code; the structure:

// ConversationActivity — the list
conversations.setOnItemClick { conversation ->
    val intent = Intent(this, MessageActivity::class.java)
    when (val entity = conversation.conversationWith) {
        is User -> intent.putExtra("user", entity)
        is Group -> intent.putExtra("group", entity)
    }
    startActivity(intent)
}
conversations.setOnSearchClick { startActivity(Intent(this, SearchActivity::class.java)) }

activity_message.xml is a vertical LinearLayout: header wrap_content → list 0dp + layout_weight="1" → composer wrap_content, all match_parent wide. The weight is what makes the list scroll instead of pushing the composer off-screen. Bind the SAME User/Group into header + list + composer, and wire header.setOnBackPress { finish() }.

Thread and search are part of this default — see Placement 3's screens; wire them or hide the affordance.

Placement 2 — a chat tab in an existing app

Host CometChatConversations in a Fragment inside your existing BottomNavigationView/ViewPager2. The tab shows the LIST; tapping a row starts the message Activity full-screen above the tab bar (not inside the tab), so the composer isn't fighting the nav bar. Reuse the host's navigation graph — additive only, never rewrite it.

Brownfield theming — the host Activity KEEPS its own theme. Kit views resolve ?attr/cometchat* tokens at inflate time: constructed with the host's context (its own Material3 theme), the list crashes on first show — InflateException … MaterialButton on ?attr/cometchatPrimaryColor (verified on device). Do NOT re-parent the <application> theme (that's core's greenfield path; it breaks a Material3 host's own screens). Scope instead:

<!-- res/values/themes.xml — your app theme stays untouched -->
<style name="Theme.YourApp.Chat" parent="CometChatTheme.DayNight">
    <item name="cometchatPrimaryColor">@color/your_brand</item>
</style>
<!-- AndroidManifest.xml: android:theme="@style/Theme.YourApp.Chat" on EACH chat Activity -->
// Fragment/tab-embedded kit views: wrap the context; the host Activity keeps its theme
val themed = android.view.ContextThemeWrapper(requireContext(), R.style.Theme_YourApp_Chat)
val conversations = CometChatConversations(themed)

Placement 3 — grow to the full app (the tab-based recipe)

The chat-experience contract, from the docs tab-based-chat page: a BottomNavigationView over four destinations — Chats (CometChatConversations) · Users (CometChatUsers) · Groups (CometChatGroups) · Calls (CometChatCallLogs) — each item click routing into the same message Activity.

⚠️ A Groups tap is NOT a Conversations tap. CometChatGroups lists groups the user may not have joined; routing one straight into the message screen renders the kit's error state with a live-but-dead composer (verified on device). Before navigating, check membership (group.isJoined — the field hasJoined is private; isJoined() is the accessor) and, when false, join first — CometChat.joinGroup(guid, groupType, password, CallbackListener<Group>) — the listener is REQUIRED (4 params) — prompting for the password when groupType is CometChatConstants.GROUP_TYPE_PASSWORD. Only a Conversations row is guaranteed joined, because a conversation only exists once you are in it.

Then, as the plan names them:

  • Detail screens — user detail (block/unblock) and group detail via CometChatGroupMembers (kick/ban/scope, role-gated).
  • Thread screen — CometChatThreadHeader + a parent-scoped list/composer:
// ThreadActivity — header takes the parent MESSAGE, list + composer take its ID (Long)
threadHeader.setParentMessage(parent)
threadList.setParentMessageId(parent.id)
threadComposer.setParentMessageId(parent.id)
// AND the conversation target, or replies silently never send:
user?.let { threadList.setUser(it); threadComposer.setUser(it) }
group?.let { threadList.setGroup(it); threadComposer.setGroup(it) }
  • Search screen — CometChatSearch; global by default, or scoped to the open chat with setUid(...)/setGuid(...), plus setSearchIn(listOf(SearchScope.CONVERSATIONS, SearchScope.MESSAGES)) (import com.cometchat.uikit.core.constants.SearchScope) and setOnConversationClick/setOnMessageClick.
  • Incoming calls at app level — CometChatIncomingCall driven by a global call listener so a call rings from any tab (→ cometchat-android-v6-calls).

Each tab keeps its own back stack.

Placement 4 — embed in an existing screen

Drop a component into a slice of your own layout: 0dp + constraints inside a ConstraintLayout, or a fixed dp height — never wrap_content. The kit fills the box you give it, so an unsized box renders nothing. Useful for a support widget or a chat panel inside a larger screen.

Placement 5 — deep entry from an arbitrary screen (known UID/GUID)

"Open a chat with THIS user" from one of your own screens (an order, an article, a ticket): gate on the same init/login, resolve the entity with the Chat SDK, then start the SAME message Activity from Placement 1 with the entity extra (docs: core → references/docs-map.md → one-to-one-chat):

CometChat.getUser(uid, object : CometChat.CallbackListener<User>() {   // groups: CometChat.getGroup(guid, …)
    override fun onSuccess(user: User) {
        startActivity(Intent(this@YourActivity, MessageActivity::class.java).putExtra("user", user))
    }
    override fun onError(e: CometChatException) { /* SURFACE it (toast/snackbar) — ERR_UID_NOT_FOUND must not dead-end */ }
})

Verified in the installed SDK: CometChat.getUser(@NonNull String, @NonNull CallbackListener<User>) / CometChat.getGroup(@NonNull String, @NonNull CallbackListener<Group>).

Sizing and the keyboard

Owned by core/references/layout.md; the two that bite in Views: android:windowSoftInputMode="adjustResize" on the message Activity (or the composer hides under the IME), and enableEdgeToEdge() + inset padding (or the header/composer sit under the system bars). Never nest CometChatMessageList in a ScrollView.

Navigation & back-stack discipline

Every pushed screen pops correctly: header setOnBackPress → finish(); thread/search/detail → back returns to the message screen; predictive-back safe. Pass User/Group as Parcelable extras (or re-fetch by uid/guid on the destination — Placement 5's getUser/getGroup — safer across process death). Never re-init/login per Activity; that happened once at startup.

Common pitfalls

Starting a chat Activity before login resolves · inflating a kit view under the host's own theme (brownfield: scoped theme + ContextThemeWrapper, Placement 2 — never re-parent the application theme of an app with its own design system) · a swallowed getUser/getGroup onError (Placement 5) · a non-lifecycle host Activity · Activity missing from the manifest · wrap_content/unsized parent · missing layout_weight="1" on the list (composer pushed off-screen) · composer under the IME · thread screen missing the user/group target · thread/search opened with no way back · nesting the message list in another scrollable.

Verify it works

Compile: ./gradlew :app:assembleDebug # in YOUR app (native-fence.mjs is a pack-repo gate, not installed by the CLI). On device: list → tap → message screen for the right entity; back returns; composer stays visible with the keyboard open; nothing under the system bars; thread and search open AND return; on the grown app each tab renders and an incoming call surfaces from any tab. A sliver or a top-left cram is a HOST sizing defect → core/references/layout.md.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

MIT

Source path

skills/cometchat-android-v6-kotlin-placement

Default branch

main

Latest commit

f92ff9e

Tree SHA

f40470f