cometchat-flutter-v5-sdk

SkillWeb & browsing

Lets your agent add voice and video calling screens to a Flutter app using the CometChat Calls SDK.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the cometchat-flutter-v5-sdk skill

About this skill

Add 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/l

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_sdk 5.0.7 from pub.dev + catalog flutter-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 the calls-core monorepo exactly — but never infer that from a version string (references/docs-map.md § "Upstream"). Official docs: /calls/flutter/** = v5 · Docs MCP. Fetch exact SessionSettings fields, 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 use cometchat-flutter-v6-calls instead.

Companion skills (read first)

  • Standalone/headless entry — this skill owns its own package (the Calls SDK) and has no -core sibling; self-contained for meet-style calling.
  • For 1:1 RINGING only it also drives the Chat SDK (cometchat_sdk v5) for signaling — signatures fetched via references/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, iOS platform :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 routine pub 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 (persists integrationSource="ai-agent").

    It takes no settings argument: it reads cometchat-settings.json through rootBundle, so the file must sit at the project root and be registered as a Flutter asset:

    flutter:
      assets:
        - cometchat-settings.json
    

    Root keys appId + region; optional callsSDK → adminHost/clientHost/callsHost. No Auth Key — the Calls SDK reads nothing else from this file (the key is a login() argument). Registering it under flutter: assets: is the ONLY step; no Xcode or Gradle change, because it is read through rootBundle. Miss the assets: 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, not setX() methods. CallAppSettingBuilder is singular (no s).

  • ⚠️ Never do both. A builder init AFTER initFromSettings re-attributes the app to "manual" and the ai-agent telemetry is lost.

  • Login: CometChatCalls.login(uid:, authKey:, onSuccess:, onError:) (dev) or CometChatCalls.loginWithAuthToken(authToken:, onSuccess:, onError:) (production). Check await CometChatCalls.getLoggedInUser() first — it returns Future<User?> and the SDK keeps the session, so logging in twice is wasted work. Both onSuccess callbacks hand you a NULLABLE User? — 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_sdk v5 as well. Fetch signatures via references/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:), or joinSession(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
  • Listeners (on CallSession.getInstance(); four are add/remove, LayoutListeners is a single-slot property): SessionStatusListeners · ParticipantEventListeners · MediaEventListeners · ButtonClickListeners · LayoutListeners
  • Call logs: CallLogRequestBuilder (top-level; FIELDS, no setX()) → 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 analyze probe). A symbol is real iff it is in the catalog — confirm there, then fetch its signature from the page in references/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 extends lets you override only what you need; implements obliges you to write them all.
  • remove*Listener takes 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 via addMediaEvent**s**Listener, and LayoutListeners has no add/remove at all — it is a single-slot layoutListener = l setter, cleared with = null.
  • CallSession.getInstance() is never actually null — the singleton is eagerly built, so registering BEFORE joinSession really does attach (certified: onSessionJoined fires). The type is still CallSession?, so keep ?.; use isCallSessionActive() to test for a live session.
  • ButtonClickListeners callbacks fire alongside the SDK's own action and do NOT suppress it. Reacting to onLeaveSessionButtonClicked must NOT also call leaveSession() — the SDK is already leaving; doing both double-leaves. Use it to run CometChat.endCall(sessionId) on the ringing path.
  • Participant has 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 (see doc-corrections.md); accumulate that live state from ParticipantEventListeners, as iOS must. Prefer onParticipantListChanged, which delivers the WHOLE list. (Participants, plural, is a @Deprecated typedef alias of Participant.)

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_notifications Flutter 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 writeIt 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 / .raisedHandTimestampNone 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) — joinSession renders nothing itself. Store the Widget? from onSuccess in State and return it from build().
  • 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*Button setters on SessionSettingsBuilder. To merely REACT to the built-in leave button, use ButtonClickListeners(onLeaveSessionButtonClicked:) — do not add a second button.
  • Removing a listener you did not keep — construct once into a field; remove*Listener matches by instance.
  • User is exported by BOTH barrels — ringing imports both SDKs; ambiguous_import until 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 with rejectCall(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.
  • minSdkVersion left at Flutter's default — the floor is 26 (docs say 24), in android/app/build.gradle.kts on current Flutter, not android/build.gradle.
  • iOS Simulator on Apple Silicon: CometChatWebRTC excludes arm64, so the app builds x86_64 — fine on an iOS 18 simulator (Rosetta), impossible on iOS 26+. Device or iOS 18 sim; details in references/platform-notes.md.
  • cometchat-settings.json not in flutter: assets: — initFromSettings fails 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 — init after initFromSettings silently re-attributes telemetry to "manual".
  • Handling onSessionLeft but not onSessionTimedOut — idle timeout (default 300s, 0 disables) 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 real startScreenShare()/stopScreenShare()/toggleScreenShare() + an isScreenSharing getter (barrel-reachable in 5.0.7; the method channel invokes a native startScreenShare and web routes to getDisplayMedia), and hideScreenSharingButton defaults to false so the built-in button is visible. So an emit calling startScreenShare() COMPILES and dispatches — do NOT tell the user local screen share is impossible or "invented." The /calls/flutter/screen-sharing DOC 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, SessionSettings fields and action params are FETCHED from docs, never guessed.

Verify it works

  • Tier-1 catalog: every cometchat_calls_sdk symbol emitted appears in flutter-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) with flutter analyze in test-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
114
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Key
cometchat-flutter-v5-sdk
Source
github.com/cometchat/cometchat-skills