cometchat-flutter-v5-sdk
SkillWeb & browsingAdd voice & video calling to any Flutter app FROM SCRATCH with the headless CometChat Calls SDK v5 (`cometchat_calls_sdk`, pub.dev), no UI Kit. init→login→generateCallToken→joinSession, which hands back a Flutter `Widget?` you mount in your tree; CallSession listeners, in-call actions (mute/video/layout/record/PiP/raise-hand), meet-style session rooms AND 1:1 ringing (Chat SDK signaling + Calls SDK media). Triggers: 'add calling from scratch flutter', 'standalone video call dart', 'headless calls sdk flutter', 'build my own call screen flutter', 'meeting room join by session id flutter', 'one-on-one ringing call without uikit flutter'.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cometchat-flutter-v5-sdk skill
What this skill tells your AI
The instructions your AI receives, as published by cometchat/cometchat-skills in skills/cometchat-flutter-v5-sdk/SKILL.md and read by ahel’s review.
Ground truth:
cometchat_calls_sdk5.0.7 from pub.dev + catalogflutter-calls-v5.json(the closed symbol list — 59 COMPILER-VERIFIED symbols; every type below is in it). Catalog built from the PUBLISHED package; at 5.0.7 it matches thecalls-coremonorepo exactly — but never infer that from a version string (references/docs-map.md§ "Upstream"). Official docs:/calls/flutter/**= v5 · Docs MCP. Fetch exactSessionSettingsfields, event names and signatures from the docs (references/docs-map.md) — never memory. ⚠️ The live pages carry ~56 symbols that DO NOT COMPILE, including the entire documented token flow — see "Doc corrections" below; prefer THIS skill's names over the page's. APPEND to the user's app — additive wiring only. This is the HEADLESS path (no UI Kit); for the prebuilt drop-in call UI usecometchat-flutter-v6-callsinstead.
Companion skills (read first)
- Standalone/headless entry — this skill owns its own package (the Calls SDK) and has no
-coresibling; self-contained for meet-style calling. - For 1:1 RINGING only it also drives the Chat SDK (
cometchat_sdkv5) for signaling — signatures fetched viareferences/docs-map.md(§ "1:1 RINGING"). It does NOT depend on the Flutter UI Kit. - Prefer the UI Kit instead? If the app also wants chat and a prebuilt call UI, use
cometchat-flutter-v6-core+cometchat-flutter-v6-calls— not this skill.
Use this skill when
"add calling from scratch / without the UI Kit", "standalone voice/video on Flutter", "build my own call screen", "headless calls SDK", "meeting-room join by session id", "one-on-one ringing". Precondition: the caller chose the from-scratch / standalone path (the "add calling" router asks this). If they want the prebuilt call UI, route to cometchat-flutter-v6-calls.
Prerequisites & install
# pubspec.yaml
dependencies:
cometchat_calls_sdk: ^5.0.7 # verified against 5.0.7
then flutter pub get.
1:1 ringing additionally needs the Chat SDK (cometchat_sdk v5) for signaling.
Permissions — REQUIRED or the call dies on the first permission prompt.
android/app/src/main/AndroidManifest.xml:INTERNET,CAMERA,RECORD_AUDIO,MODIFY_AUDIO_SETTINGS,ACCESS_NETWORK_STATE. On API 23+ you must ALSO request camera + mic at runtime — the manifest alone is not enough, and this is the single most common "the SDK is broken" report on Android.ios/Runner/Info.plist:NSCameraUsageDescription+NSMicrophoneUsageDescription.- Android
minSdkVersion 26, iOSplatform :ios, '15.1'— the real 5.0.7 floors. The docs say 24 and iOS 12; both fail at BUILD time. iOS moved 13.0 → 15.1 in 5.0.7, so an app can break on a routinepub upgrade.
Credentials: App ID · Region · Auth Key (dev only), via the CLI — npx @cometchat/skills-cli@3 auth login → provision use --app-id <id> --json (@3 pins the CLI major that matches the v5 skills). Mint auth tokens server-side for production; never ship the Auth Key.
Init & login ordering (BAKED — invariant)
Init (once, must succeed) → CometChatCalls.login(...) → then generateCallToken / joinSession / listeners. Nothing joins before init+login resolve. CometChatCalls.isInitialized is the guard.
-
DEFAULT to
CometChatCalls.initFromSettings(onSuccess:, onError:)— the ai-agent / telemetry-attributed path (persistsintegrationSource="ai-agent").It takes no settings argument: it reads
cometchat-settings.jsonthroughrootBundle, so the file must sit at the project root and be registered as a Flutter asset:flutter: assets: - cometchat-settings.jsonRoot keys
appId+region; optionalcallsSDK→adminHost/clientHost/callsHost. No Auth Key — the Calls SDK reads nothing else from this file (the key is alogin()argument). Registering it underflutter: assets:is the ONLY step; no Xcode or Gradle change, because it is read throughrootBundle. Miss theassets:entry and init fails with settings-file-not-found — a quiet callback, not a crash. -
Fallback (public-doc path):
CallAppSettings s = (CallAppSettingBuilder()..appId = appId..region = region).build(); CometChatCalls.init(s, onSuccess: (String m) {}, onError: (CometChatCallsException e) {});Note the Dart cascade (
..appId =) — these are fields, notsetX()methods.CallAppSettingBuilderis singular (nos). -
⚠️ Never do both. A builder init AFTER
initFromSettingsre-attributes the app to"manual"and the ai-agent telemetry is lost. -
Login:
CometChatCalls.login(uid:, authKey:, onSuccess:, onError:)(dev) orCometChatCalls.loginWithAuthToken(authToken:, onSuccess:, onError:)(production). Checkawait CometChatCalls.getLoggedInUser()first — it returnsFuture<User?>and the SDK keeps the session, so logging in twice is wasted work. BothonSuccesscallbacks hand you a NULLABLEUser?— the docs dereference it unguarded; do not copy that.
Two build modes (BAKED — the router picks one)
- Meet-style (session room) — Calls SDK ONLY, no Chat SDK. Everyone joining the same session id lands in the same call.
- 1:1 ringing — Chat SDK signals, Calls SDK carries media. Needs
cometchat_sdkv5 as well. Fetch signatures viareferences/docs-map.md§ "1:1 RINGING".
⚠️ The Flutter fork: joinSession returns a WIDGET
Every other platform mounts the call into a container you hand it. Flutter hands the call back to
you as a Widget? and mounts nothing:
Widget? _callWidget;
CometChatCalls.joinSession(
sessionId: sessionId,
sessionSettings: settings,
onSuccess: (Widget? w) => setState(() => _callWidget = w), // ← THIS is the render step
onError: (CometChatCallsException e) { /* surface it */ },
);
@override
Widget build(BuildContext c) => Scaffold(body: _callWidget ?? const CircularProgressIndicator());
An emit that ignores the onSuccess argument joins the call successfully and shows nothing — audio flows, no video, no controls. It reads as a broken SDK and it is the #1 Flutter-specific failure. There is no container parameter and no zero-bounds trap; there is only "did you put the widget in the tree".
SDK method map (BAKED closed list — from the catalog; signatures → FETCH from docs)
- Lifecycle:
CometChatCalls.initFromSettings·CometChatCalls.init·CallAppSettingBuilder→CallAppSettings·CometChatCalls.isInitialized·login·loginWithAuthToken·logout·getLoggedInUser→User? - Session:
CometChatCalls.generateCallToken(sessionId, …)→CallToken·joinSession(…)→Widget?·SessionSettingsBuilder→SessionSettings·SessionType - ⚠️ TWO join paths, both real in 5.0.7 — pick one:
generateCallToken(sessionId)→joinSession(callToken:sessionSettings:), orjoinSession(sessionId:sessionSettings:)directly. - In-call actions — on
CallSession.getInstance(), for CUSTOM controls ONLY (the returned widget already renders them; see pitfall #1):leaveSession·muteAudio/unMuteAudio·pauseVideo/resumeVideo·setLayout·setAudioModeType·switchCamera·raiseHand/lowerHand·startRecording/stopRecording·startScreenShare/stopScreenShare/toggleScreenShare·pinParticipant/unPinParticipant·muteParticipant·pauseParticipantVideo·enablePictureInPictureLayout/disablePictureInPictureLayout·enterPipMode/isPipSupported·setChatButtonUnreadCount·isCallSessionActive- Toggles (Flutter-only, no iOS analogue):
toggleMuteAudio·togglePauseVideo·toggleCameraSource·toggleRaiseHand - Aliases for the same actions:
endCallSession·startCallRecording/stopCallRecording·setCallLayoutType - Settings panels:
hideSettingsPanel·openCallSettingsPanel·openVirtualBackgroundSettingsPanel - State getters (read, don't track):
isAudioMuted·isVideoPaused·isHandRaised·isRecording·isScreenSharing·isTranscribing
- Toggles (Flutter-only, no iOS analogue):
- Listeners (on
CallSession.getInstance(); four are add/remove,LayoutListenersis a single-slot property):SessionStatusListeners·ParticipantEventListeners·MediaEventListeners·ButtonClickListeners·LayoutListeners - Call logs:
CallLogRequestBuilder(top-level; FIELDS, nosetX()) →CallLogRequest→fetchNext/fetchPrevious→CallLog(+CallUser/CallGroup/CallEntity) - Background:
CometChatOngoingCallService.launch()/.abort()— Android foreground service, no-op on iOS - Enums:
AudioMode(.speaker/.earpiece/.bluetooth) ·SessionType·LayoutType(.tile/.spotlight/.sidebar) ·CameraFacing
A curated highlight, NOT the full surface. The authoritative closed list is the catalog (59 symbols, each proven by a
flutter analyzeprobe). A symbol is real iff it is in the catalog — confirm there, then fetch its signature from the page inreferences/docs-map.md.
Listener lifecycle (BAKED)
All five listener types are abstract classes you IMPLEMENT — they are NOT constructible.
The live docs build every one of them with named callbacks
(SessionStatusListeners(onSessionJoined: () {…})); an abstract class cannot be instantiated, so
none of those examples compile. flutter analyze reports instantiate_abstract_class.
class _Status extends SessionStatusListeners {
@override void onSessionJoined() { /* ... */ }
@override void onSessionTimedOut() { /* distinct from onSessionLeft — handle BOTH */ }
// `extends` instead of `implements` and the other four inherit their no-op bodies.
}
final _status = _Status(); // hoist into a FIELD
CallSession.getInstance()?.addSessionStatusListener(_status); // BEFORE joining
// teardown:
CallSession.getInstance()?.removeSessionStatusListener(_status); // the SAME instance
CallSession.getInstance()?.leaveSession();
- Every method already has a no-op body, so
extendslets you override only what you need;implementsobliges you to write them all. remove*Listenertakes the object you added. Constructing a fresh listener to remove one is a no-op that silently leaks the original — and it is what the live docs show (D4). Hoist into a field; there is no other shape in which removal works.- ⚠️ Registration names do NOT match the type names — full table in
references/docs-map.md. Two you cannot guess:MediaEvent**Listeners**registers viaaddMediaEvent**s**Listener, andLayoutListenershas no add/remove at all — it is a single-slotlayoutListener = lsetter, cleared with= null. CallSession.getInstance()is never actually null — the singleton is eagerly built, so registering BEFOREjoinSessionreally does attach (certified:onSessionJoinedfires). The type is stillCallSession?, so keep?.; useisCallSessionActive()to test for a live session.ButtonClickListenerscallbacks fire alongside the SDK's own action and do NOT suppress it. Reacting toonLeaveSessionButtonClickedmust NOT also callleaveSession()— the SDK is already leaving; doing both double-leaves. Use it to runCometChat.endCall(sessionId)on the ringing path.Participanthas TWELVE fields, all nullable:uid·name·avatar·mid·state·isJoined·deviceId·joinedAt·leftAt·totalAudioMinutes·totalVideoMinutes·totalDurationInMinutes. No mute/pin/hand/share flags — the seven the docs' "Participant Object Reference" lists (pid/role/audioMuted/videoPaused/isPinned/isPresenting/raisedHandTimestamp) do NOT exist and do not compile (seedoc-corrections.md); accumulate that live state fromParticipantEventListeners, as iOS must. PreferonParticipantListChanged, which delivers the WHOLE list. (Participants, plural, is a@Deprecatedtypedef alias ofParticipant.)
Least-code recipe (meet-style — ORDER, not signatures)
// Fetch every signature/field from references/docs-map.md. This is the ORDER, which is the part that bites.
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
1. CometChatCalls.initFromSettings(onSuccess:, onError:) // ai-agent default; cometchat-settings.json as a Flutter ASSET
2. CometChatCalls.login(uid:, authKey:, onSuccess:, onError:) // or loginWithAuthToken in production
3. final settings = (SessionSettingsBuilder()..setDisplayName(name)..setType(SessionType.video)).build();
4. CallSession.getInstance()?.addSessionStatusListener(_status); // register BEFORE joining
5. CometChatCalls.joinSession(sessionId:, sessionSettings:, onSuccess: (Widget? w) => setState(() => _call = w), onError:)
// or: generateCallToken(sessionId) -> joinSession(callToken:sessionSettings:)
6. // return _call from build() — NOTHING renders until you do
7. // NO control buttons needed — the returned widget ALREADY renders mute/video/leave.
8. // teardown -> remove each listener (same instance); CallSession.getInstance()?.leaveSession()
Backgrounding & call notifications → references/platform-notes.md
Two things that are NOT on the join path and are easy to get wrong:
- Backgrounding — Android kills a call the moment the app backgrounds unless
CometChatOngoingCallService.launch()holds a foreground service (.abort()on teardown). Flutter-only; both calls are safe no-ops on iOS. The manifest permissions are not optional. - Ringing a closed app — there is no first-party CometChat Flutter push package (iOS has
one; Flutter does not), and no symbol in this skill's catalog implements it. It is FCM on
Android and APNs/PushKit/CallKit on iOS, wired by the app. Needs a dashboard push provider and a
physical device. An emit naming a
cometchat_push_notificationsFlutter package invented it.
Doc corrections (⚠️ the live pages do not compile — full table: references/doc-corrections.md)
23 classes of symbol on /calls/flutter/** fail flutter analyze against 5.0.7. Read
references/doc-corrections.md before copying off a page. The two that bite hardest:
| The docs write | It does not compile — use |
|---|---|
generateToken(sessionId: …) → CallToken (13 pages) | generateCallToken(sessionId, onSuccess:, onError:) → CallToken. generateToken is @Deprecated, takes two POSITIONAL args, and yields GenerateToken. No page mentions generateCallToken at all |
Participant.pid / .role / .audioMuted / .videoPaused / .isPinned / .isPresenting / .raisedHandTimestamp | None exist. Participant has 7 fields; track the rest from events |
Docs PR cometchat/docs#492 fixes the first seven classes (56 → 3 analyzer errors). Five more —
including the abstract-listener one — were found by running flutter analyze over this skill's
own harness AFTER #492 was open, and are not in it. 12 fences on those pages parse in no shape
and were never machine-checked, so the table is a floor, not a total.
Common pitfalls (BAKED)
- Forgetting to mount the returned widget (the #1 Flutter mistake) —
joinSessionrenders nothing itself. Store theWidget?fromonSuccessin State and return it frombuild(). - Don't duplicate the built-in call controls (the #1 mistake everywhere else). The joined widget renders a COMPLETE call UI — mute, camera, layout, participants, leave. Do NOT add your own buttons around it. The
CallSession.getInstance()action methods are for CUSTOM controls ONLY — reach for them just when the user EXPLICITLY asks to replace the defaults, and hide the built-ins first via the..hide*Buttonsetters onSessionSettingsBuilder. To merely REACT to the built-in leave button, useButtonClickListeners(onLeaveSessionButtonClicked:)— do not add a second button. - Removing a listener you did not keep — construct once into a field;
remove*Listenermatches by instance. Useris exported by BOTH barrels — ringing imports both SDKs;ambiguous_importuntil one hides it (… hide User;). No page says so.- Ringing teardown needs BOTH SDKs —
leaveSession()ends your media,CometChat.endCall(sessionId)tells the peer and completes the log. Only the first, and the peer still thinks the call is live. - There is no
cancelCall— cancel your own outgoing call withrejectCall(sessionId, CometChatCallStatus.cancelled). - Manifest permissions without the runtime request — on Android 6+ the manifest alone grants nothing; request camera/mic before joining or the call is black and silent.
minSdkVersionleft at Flutter's default — the floor is 26 (docs say 24), inandroid/app/build.gradle.ktson current Flutter, notandroid/build.gradle.- iOS Simulator on Apple Silicon:
CometChatWebRTCexcludes arm64, so the app builds x86_64 — fine on an iOS 18 simulator (Rosetta), impossible on iOS 26+. Device or iOS 18 sim; details inreferences/platform-notes.md. cometchat-settings.jsonnot influtter: assets:—initFromSettingsfails with settings-file-not-found. It is a quiet error callback, not a crash. Registering the asset is the whole job; no Xcode step.- Double init —
initafterinitFromSettingssilently re-attributes telemetry to"manual". - Handling
onSessionLeftbut notonSessionTimedOut— idle timeout (default 300s,0disables) fires a DIFFERENT callback; miss it and the user sits on a dead call screen. - Emitting an API Flutter lacks — no presentation mode, no first-party push package. Say so rather than emitting code that cannot resolve. The two PiPs are also different calls:
enablePictureInPictureLayout()is the in-app layout,enterPipMode()is Android system PiP. - Screen share — the control methods DO ship (docs lag them).
CallSession.getInstance()has realstartScreenShare()/stopScreenShare()/toggleScreenShare()+ anisScreenSharinggetter (barrel-reachable in 5.0.7; the method channel invokes a nativestartScreenShareand web routes togetDisplayMedia), andhideScreenSharingButtondefaults tofalseso the built-in button is visible. So an emit callingstartScreenShare()COMPILES and dispatches — do NOT tell the user local screen share is impossible or "invented." The/calls/flutter/screen-sharingDOC page still says "receive-only" — it documents only the RECEIVE side (onParticipantStartedScreenShare,participant.isPresenting, viewing a web participant's share); the shipped local-share control API is undocumented there. Reflect the shipped methods, and note the page lags them. - Joining before init+login resolve — chain from the success callbacks.
- Assuming a signature — event names,
SessionSettingsfields and action params are FETCHED from docs, never guessed.
Verify it works
- Tier-1 catalog: every
cometchat_calls_sdksymbol emitted appears influtter-calls-v5.json(node test-suite/scripts/verify-catalog.mjs --family flutter-calls-v5). - Tier-2 compile: the emit analyzes clean against the installed
cometchat_calls_sdk(5.0.7) withflutter analyzeintest-suite/harness/flutter-calls. - Tier-3 smoke: the round-trip (init→login→generateCallToken→join→widget-mounted→leaveSession) via the pack's dev calls-smoke harness (internal QA, not shipped) — certified on a physical Android device. Ringing runs as TWO concurrent devices, one process per role, and only counts if both succeed on the SAME session id — see
references/docs-map.md§ "1:1 RINGING".
Signals
- GitHub stars
- 109
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
cometchat-flutter-v5-sdk- Source
- github.com/cometchat/cometchat-skills