Ground truth: every listener symbol below is catalog-verified against 6.1.0. There are TWO event systems and they are not interchangeable — the exact callback set for each is FETCHED from
events(UI events) and the SDK'sreal-time-listenersvia../cometchat-flutter-v6-core/references/docs-map.md.
Companion skills (read first)
cometchat-flutter-v6-core— install, credentials,init→login→render. This skill ASSUMES it.
Use this skill when
"do something when a message arrives", "update a badge count", "refresh my screen when a group changes", "cometchat listener" — anything reactive OUTSIDE what the kit's widgets already do for themselves.
Prerequisites & install
Covered by core. No new package.
First: do you actually need a listener?
The kit's widgets are already live. CometChatConversations updates its own unread counts, CometChatMessageList appends incoming messages, CometChatMessageHeader shows typing and presence — all without a single listener from you. Add one only for something OUTSIDE the kit's surface: an app-level badge, an analytics hook, a push registration, navigating on an incoming call.
Adding a listener to "make the list update" is the most common mistake here — it already does.
The two systems (BAKED)
| SDK listeners | UI Kit events | |
|---|---|---|
| Source | the server, via CometChat.* | the kit's own widgets |
| Register | CometChat.addMessageListener(id, this) | CometChatMessageEvents.addMessagesListener(id, this) |
| Mixin | MessageListener · CallListener · UserListener · GroupListener · ConnectionListener | CometChatMessageEventListener · CometChatGroupEventListener · CometChatUserEventListener · CometChatConversationEventListener |
| Answers | "the server says X happened" | "the user did X in the kit" |
| Use for | badges, push, incoming calls, analytics | reacting to a kit action (message sent from the composer, group left via the kit) |
SDK listener — server-side truth:
dartimport 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart'; import 'package:flutter/material.dart'; class BadgeHost extends StatefulWidget { const BadgeHost({super.key}); State<BadgeHost> createState() => _BadgeHostState(); } class _BadgeHostState extends State<BadgeHost> with MessageListener { static const _id = "app_badge"; void initState() { super.initState(); CometChat.addMessageListener(_id, this); } void dispose() { CometChat.removeMessageListener(_id); // ALWAYS remove — see lifecycle below super.dispose(); } void onTextMessageReceived(TextMessage message) { // bump an app-level unread badge } Widget build(BuildContext context) => const SizedBox.shrink(); }
UI Kit event — react to what the kit did:
dartimport 'package:cometchat_chat_uikit/cometchat_chat_uikit.dart'; import 'package:flutter/material.dart'; class ComposerWatcher extends StatefulWidget { const ComposerWatcher({super.key}); State<ComposerWatcher> createState() => _ComposerWatcherState(); } class _ComposerWatcherState extends State<ComposerWatcher> with CometChatMessageEventListener { static const _id = "composer_watch"; void initState() { super.initState(); CometChatMessageEvents.addMessagesListener(_id, this); } void dispose() { CometChatMessageEvents.removeMessagesListener(_id); super.dispose(); } Widget build(BuildContext context) => const SizedBox.shrink(); }
The listener lifecycle (the rule that prevents 90% of event bugs)
- Register in
initState, remove indispose— always paired. - A unique, stable listener id per screen. Reusing one id across screens means the later registration silently replaces the earlier.
- Removing matters more than you think in Flutter: hot restart and route re-entry both re-run
initState. A listener you never removed fires again, so a badge double-counts and a call dialog opens twice. - Register AFTER login. A listener added before
loginresolves receives nothing.
Do NOT mix both message listeners on one class (compile trap — verified vs 6.1.0)
MessageListener (SDK) and CometChatMessageEventListener (UI Kit) both declare onCardMessageReceived, but with two different CardMessage types — one from cometchat_sdk, one from the kit. Mixing them on the same State fails to compile with invalid_override. Use two separate classes (or two States) when you need both.
Common pitfalls (BAKED)
- Adding a listener to make a kit widget update — it already updates itself.
- No
disposeremoval → duplicate events after hot restart / re-entry. - A shared listener id → one screen silently unregisters another.
- Both message mixins on one class → does not compile (above).
- Registering before login → silence.
- Expecting UI events for server activity (or vice-versa) — pick the right system from the table.
- v5 event APIs — the v5
DataSource/ChatConfiguratorevent plumbing is gone (→-migration).
Verify it works
The reaction fires once (not twice) per event; hot-restart the app and confirm it still fires exactly once; navigate away and back and confirm no duplicate; with the app backgrounded, server-side events still arrive when it returns.

