Ground truth:
cometchat_chat_uikit@6(6.1.0) +cometchat_sdk@^5.0.6. EveryCometChat*symbol here is incatalogs/flutter-v6.json— compiler-verified against the published package. Signatures marked [verified] were compiled because the docs were wrong, or (forinitFromSettings) deliberately silent (DOCS-BACKLOG.mdPART 4): trust this file for those, fetch the rest viareferences/docs-map.md. THIN map loaded every run; depth inreferences/*.
Use this skill when
"add chat to my Flutter app", "integrate CometChat in Flutter", "set up CometChat credentials", "show a conversations + messages screen". An unscoped "add chat" means a production-ready core surface — a CometChatConversations screen pushing a message screen, with threaded replies and search wired, in Scaffold/SafeArea, keyboard-safe, every screen popping back — NOT a two-widget demo, NOT the whole tab-based app (see the golden path). It grows on request → cometchat-flutter-v6-placement.
Install
bashflutter pub add cometchat_chat_uikit
Pin the major in pubspec.yaml — cometchat_chat_uikit: ^6.1.0 (a bare any can resolve across majors). The Chat SDK (cometchat_sdk) comes transitively and is re-exported by the kit barrel — do NOT add it separately unless you import SDK-only paths. Voice/video calling = a settings flag + native config (cometchat_calls_sdk is already a transitive kit dependency — see below) → cometchat-flutter-v6-calls.
Platform minimums — Android minSdk = 26 in android/app/build.gradle.kts; iOS platform :ios, '15.1' in ios/Podfile. The getting-started page says 24 / 13.0 and both are too low (DOCS-BACKLOG D7). The kit's own AAR allows 21, but transitive deps set the real floor and this one is not optional: cometchat_chat_uikit depends on cometchat_calls_sdk unconditionally — it sits in the main dependencies: block — and that package pins minSdkVersion 26 / platform :ios, '15.1' (5.0.7). Gradle and CocoaPods take the maximum across the graph, so a lower value is a build error whether or not the app uses calling.
Setup & credentials (essentials — full detail: references/setup-credentials.md)
- Detect by READING the repo —
pubspec.yaml+ lockfile (deps and the kit major),lib/main.dart, state management, navigation, and how config is handled. Reuse an existing.cometchat/config.jsonor settings asset (skip re-setup). Signal table:references/setup-credentials.md. - version_conflict — STOP if the project already has
cometchat_chat_uikitv5 (or any non-v6 major); reconcile first, never mix majors (RULES.md). A v5 project that wants v6 →cometchat-flutter-v6-migration. - Credentials — OFFER the dashboard fetch FIRST; never default to "paste it yourself." When App ID / Region / Auth Key are missing, offer (a) fetch from your dashboard (recommended) — load the CLI on demand, pick an EXISTING app (never auto-create) — or (b) manual paste. Then the SKILL writes
cometchat-settings.json(§4) from those creds; the CLI is dashboard/API-only and never writes Dart (RULES.md§21). Never defer credentials to a "add them yourself" TODO. Exact commands + the app-picker flow:references/setup-credentials.md. - Write the settings asset + register it — commit only a placeholder (no real Auth Key) and do not gitignore it (a registered asset must exist or a fresh-clone
flutter buildfails — see the note in §Integration ordering); never echo the Auth Key. Prod → server-minted auth token, not the Auth Key. - Authorize =
initthenloginboth hitonSuccess. An auth error is almost always the wrong Region.
Integration ordering (BAKED — invariant; docs: init must complete before login, login before any widget)
initFromSettings() once at startup → login(UID) after init succeeds → only THEN render any CometChat* widget.
Init via initFromSettings — the file-based, telemetry-attributed path (RULES.md §5). It reads a cometchat-settings.json asset and persists integrationSource="ai-agent". Flutter's signature takes NO settings argument (unlike web, which passes a settings object) — the file IS the input:
dart// [verified vs 6.1.0] — INTENTIONALLY undocumented upstream (@nodoc, owner decision: // DOCS-BACKLOG F4 WONTFIX). Prefer it over the docs' UIKitSettingsBuilder + init() // path, which loses telemetry attribution. The shape below is the contract. import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart'; await CometChatUIKit.initFromSettings( onSuccess: (String message) => debugPrint('CometChat initialized'), onError: (CometChatException e) => debugPrint('Init failed: ${e.message}'), );
cometchat-settings.json at the project root, registered in pubspec.yaml under flutter: assets::
json{ "appId": "APP_ID", "region": "REGION", "credentials": { "authKey": "AUTH_KEY" }, "uiKit": { "subscribePresenceForAllUsers": true, "enableCalling": false }, "chatSDK": { "autoEstablishSocketConnection": true } }
Keys are read exactly as above.
uiKit.enableCallingis the only way to turn calling on via this path (UIKitSettingsdefaults it to false) — leave itfalseuntil the user asks for calls.credentials.authKeyis dev-only; omit it for production and log in with a server-minted token. Make a fresh clone build. A file registered underflutter: assets:MUST exist at build time orflutter build bundlefails with "No file or variants found for asset: cometchat-settings.json." So do not gitignore the registered asset. Instead commit a placeholdercometchat-settings.json— same shape, with a placeholder/emptyauthKey(NEVER a real dev Auth Key) — and have each developer and CI overwrite it locally/at build time with real credentials. (Enforce "no real key committed" via a pre-commit check or CI, not.gitignore.) For production, CI generates the prod-flavoured file (no Auth Key) at build time (cometchat-flutter-v6-production).
dart// login AFTER init succeeds. CometChatUIKit.login already checks getLoggedInUser() // internally and no-ops if that UID is signed in, so no host-side re-login guard is needed. await CometChatUIKit.login( uid, onSuccess: (User user) => debugPrint('Logged in: ${user.name}'), onError: (CometChatException e) => debugPrint('Login failed: ${e.message}'), );
Which UID?
login()needs a user that ALREADY EXISTS — never invent one. ASK, or take one from Dashboard → Users; fresh apps seedcometchat-uid-1…. The only tentative suggestion iscometchat-uid-1, labelled "if this is a fresh app." Prod →CometChatUIKit.loginWithAuthToken(authToken, …)with a token minted server-side. Init-once, hot-restart, and gating the first frame:references/lifecycle.md.
Widget / API map (BAKED closed list — from the catalog)
Init/session: CometChatUIKit (initFromSettings · login · loginWithAuthToken · logout · loggedInUser), UIKitSettingsBuilder, CometChatSubscriptionType, CometChatException.
Core surface: CometChatConversations, CometChatMessageHeader, CometChatMessageList, CometChatMessageComposer; wired affordances CometChatThreadedHeader, CometChatSearch.
Grow set (added on request): CometChatUsers, CometChatGroups, CometChatGroupMembers, CometChatChangeScope, CometChatCallLogs. Incoming calls need NO widget — with calling on, the kit presents the overlay itself (-calls).
TWO BARRELS — a real compile trap.
package:cometchat_chat_uikit/cometchat_chat_uikit.dartgives you the chat widgets and the re-exported Chat SDK (User,Group,Conversation,BaseMessage,TextMessage— no separate import). Every calls symbol (CometChatIncomingCall,CometChatOutgoingCall,CometChatCallButtons,CometChatCallLogs,CometChatCallBubble,CometChatCalls) lives ONLY inpackage:cometchat_chat_uikit/cometchat_calls_uikit.dart, and that barrel does NOT re-export the chat widgets — a calls screen imports both. Verified: usingCometChatIncomingCallwith only the chat import fails to compile. The v6 widget isCometChatThreadedHeader—CometChatThreadHeader(the React name) does NOT exist here. Never carry a name across families.
Hot-path props (BAKED — the golden path needs NO fetch)
CometChatConversations:onItemTap(Conversation)(select → push the message screen) ·hideSearch·searchReadOnly(settrueso the field acts as a button) ·onSearchTap(→ pushCometChatSearch) ·conversationsRequestBuilder(scope touser/groupwhen the ask is DM-only or groups-only — reuse the builder, don't client-filter) ·hideAppbar·appBarOptions·onBack.CometChatMessageHeader:user:/group:(pass the SAME target as the list) ·showBackButton(defaulttrue) +onBack·hideVoiceCallButton/hideVideoCallButton(call buttons are built in — leave them unless calls are out of scope) ·auxiliaryButtonView/trailingView(where a custom action goes — see search below).CometChatMessageList:user:/group:·parentMessageId(thread mode) ·onThreadRepliesClick[verified] — the type isThreadRepliesClick = void Function(BaseMessage message, BuildContext context, {CometChatMessageTemplate? template}); a one-argument closure does not compile (DOCS-BACKLOGF2).CometChatMessageComposer:user:/group:·parentMessageId(thread mode). A plain send needs nothing else.CometChatSearch[verified]:onConversationClicked/onMessageClicked(not the docs'onConversationItemClick/onMessageItemClick—DOCS-BACKLOGF3) ·onBack·user:/group:/searchInto scope it to one chat.
Any OTHER widget or a rarer prop → fetch its
.mdtwin viareferences/docs-map.md. Full catalog:cometchat-flutter-v6-components.
Golden path — the production-ready CORE surface (default for an unscoped "add chat"; grows on request)
Setup → init/login → gate the first frame on both resolving (show a loader; never render a CometChat* widget before login succeeds) → then:
- Conversations screen —
CometChatConversationsinsideScaffold+SafeArea. WireonItemTap→Navigator.pushthe message screen for that conversation'sUser/Group. Wire the global search the documented way:searchReadOnly: true+onSearchTap→ push aCometChatSearchscreen, whoseonConversationClicked/onMessageClickednavigate to the hit and whoseonBackpops. (Or sethideSearch: true— but never leave the field dead.) - Message screen —
CometChatMessageHeader+Expanded(child: CometChatMessageList(...))+CometChatMessageComposer, all carrying the SAMEuser:/group:.Expandedis what makes the list fill the screen; the header/composer keep their intrinsic height. LeaveScaffold.resizeToAvoidBottomInsetat its defaulttrueso the composer rides above the keyboard. The header'sshowBackButtondefaults totrue— wireonBacktoNavigator.pop. - Thread screen (DEFAULT — include unless the user opts out) — wire the list's
onThreadRepliesClickto push a screen withCometChatThreadedHeader(parentMessage:, loggedInUser:)— both required, and it has noonBack, so the screen's ownAppBarprovides back — plus aCometChatMessageList+CometChatMessageComposerthat each takeparentMessageId: parentMessage.idAND the sameuser:/group:as the parent chat. PassingparentMessageIdwithout the user/group target makes thread replies silently never send. - In-chat scoped search — Flutter's
CometChatMessageHeaderhas no search prop (unlike React). Put the action in the header'sauxiliaryButtonView/trailingViewslot — custom UI goes IN the slot, never stacked on top — and pushCometChatSearch(user: …/group: …)so it searches only that chat.
dart// [verified vs 6.1.0] the message screen — the shape the whole surface hangs off. import 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart'; import 'package:flutter/material.dart'; class MessageScreen extends StatelessWidget { final User? user; final Group? group; const MessageScreen({super.key, this.user, this.group}); Widget build(BuildContext context) { return Scaffold( // resizeToAvoidBottomInset defaults to true = keyboard-safe body: SafeArea( child: Column( children: [ CometChatMessageHeader(user: user, group: group, onBack: () => Navigator.pop(context)), Expanded( // REQUIRED — the list fills the remaining height child: CometChatMessageList( user: user, group: group, onThreadRepliesClick: (BaseMessage message, BuildContext context, {CometChatMessageTemplate? template}) { Navigator.push(context, MaterialPageRoute( builder: (_) => ThreadScreen(parentMessage: message, user: user, group: group), )); }, ), ), CometChatMessageComposer(user: user, group: group), ], ), ), ); } } // The thread screen the callback pushes — part of the DEFAULT surface, not an extra. // NOTE: the list AND composer each take parentMessageId *and* the same user:/group: // as the parent chat. parentMessageId alone => replies silently never send. class ThreadScreen extends StatelessWidget { final BaseMessage parentMessage; final User? user; final Group? group; const ThreadScreen({super.key, required this.parentMessage, this.user, this.group}); Widget build(BuildContext context) { return Scaffold( // The thread screen's back affordance is the AppBar — CometChatThreadedHeader // has NO onBack/onError param (the docs show one; it does not exist, DOCS-BACKLOG F5). // A pushed route gets the back button automatically. appBar: AppBar(title: const Text('Thread')), body: SafeArea( child: Column( children: [ CometChatThreadedHeader( // note the 'ed' — see the map above parentMessage: parentMessage, // NOT `message:` (DOCS-BACKLOG F1) loggedInUser: CometChatUIKit.loggedInUser!, // required ), Expanded( child: CometChatMessageList( user: user, group: group, parentMessageId: parentMessage.id, ), ), CometChatMessageComposer( user: user, group: group, parentMessageId: parentMessage.id, ), ], ), ), ); } }
SIZE it the mobile way —
references/layout.md. The kit's list widgets expand to fill their parent, so the failure mode here is an unbounded height error or a collapsed list, and the fix is always the same: give the list a bounded box (Expanded/Flexibleinside aColumn, or a sized parent) instead of letting content drive it. Never nest the surface in an unboundedSingleChildScrollView/ColumnwithoutExpanded. Match the data scope to the REQUEST. Real apps ship seeded groups, so a plainCometChatConversationslists both 1:1s and groups. A DM-only ask scopes the list to user conversations viaconversationsRequestBuilder; a groups-only app scopes to groups; a generic "add chat" keeps BOTH. Reuse the builder — never hand-roll a client-side filter, and don't add a Groups tab to a 1:1-only app. Prod auth: mint a per-user auth token server-side and callloginWithAuthToken— the Auth Key is dev-only (references/lifecycle.md).
Growing on request
Add each only when asked: Users / Groups / Calls tabs (CometChatUsers · CometChatGroups · CometChatCallLogs) · group details (CometChatGroupMembers + CometChatChangeScope) · calls (→ -calls; incoming calls = only navigatorKey: CallNavigationContext.navigatorKey on the root MaterialApp — never mount CometChatIncomingCall yourself, that doubles the kit's overlay) · features (→ -features). The full tab-based app is the union of these — recipe in -placement. A scoped-DOWN ask (chat tab, bottom sheet, embedded panel) → the matching -placement variant.
Deep references (load ONLY when the task needs them)
setup-credentials.md— detect signals · version_conflict · dashboard creds · the settings asset.lifecycle.md— init-once + hot restart · gating the first frame · prod auth-token · logout.layout.md— mobile sizing (bounded height ·Expanded· SafeArea · keyboard).docs-map.md— intent → live doc page (component twins + the SDK-docs section) · DOCS-GAP protocol · the pages that are wrong.anti-patterns.md— v5 carry-overs · unbounded height · render-before-login · the two-barrel trap.troubleshooting.md— symptom → cause → fix.
Common pitfalls (top 5 — full list in references/anti-patterns.md)
- version_conflict (v5 installed) · wrong Region · Auth Key shipped to prod · rendering a widget before
loginsucceeds. - Emitting v5 architecture into a v6 app. v6 REMOVED the extension pattern — no
UIKitSettings.extensions, noCometChatUIKitChatExtensions, noDataSource/ChatConfigurator/*ExtensionDecorator, noCometChatUIKit.getDataSource(). Dashboard-enabled extensions surface automatically with zero client code. Also gone: the v5 combined shells (CometChatConversationsWithMessages,CometChatUsersWithMessages,CometChatMessages,CometChatUI) — v6 has no shell widget; the host composes screens. Full map:cometchat-flutter-v6-migration. - Trusting the docs on the
[verified]signatures above. The thread-header params,onThreadRepliesClick, and the search callbacks were wrong on the live pages (DOCS-BACKLOG.mdPART 4 F1/F2/F3/F5) — all four are now corrected upstream (re-verified againstcometchat/docs@2ebb1db, 2026-09-09), so the[verified]names now agree with the pages; keep them.initFromSettingsis different: it is deliberately undocumented and stays that way (F4 WONTFIX), so this skill is its only reference.
Verify it works
Run the app: init then login both hit onSuccess → the conversations screen renders full-size (no sliver, no unbounded-height exception) → tap a conversation → messages send/receive → "reply in thread" opens the thread screen and back returns → the search field pushes search, a result navigates, back pops → the composer stays visible with the keyboard open → every pushed screen pops back. Blank screen ⇒ rendered before login resolved, or wrong Region/App ID. No bounded height, no thread wiring, or a dead search field ⇒ under-delivery. Tab-based app ⇒ verify per -placement.
Build the feature; do not write tests or run a test framework unless asked (
RULES.md) — the above is an advisory human pass.
Explain what you built (REQUIRED close — RULES.md §19)
Don't end on a silent code dump. Briefly: (1) what I wired (3–5 bullets, NAME the files); (2) decisions & why, flagging dev-only AS dev-only (Auth Key → server token for prod; the <uid>); (3) what I did NOT touch (additive — routing/state/theme intact). Then offer THREE options as a selectable choice and WAIT for the pick:
- ① Add another feature — first check what's already wired, and ASK which dashboard extensions are already on (you can't read per-app state). Suggest 3–4 that are neither (calls · push · polls · stickers · translation · AI smart replies · moderation). Never suggest reactions, mentions, quoted reply or threads — they are ON by default in v6; offer to customize them instead. →
-features/-calls. - ② Customize theming — ask for a brand/preset theme or talk through options →
-customization. - ③ Test it manually — do NOTHING further; hand it back.
A capability not in this pack: say so honestly + link CometChat's docs.

