cometchat-flutter-v5-sdk

SkillWeb & browsing

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/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.

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_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 loginprovision 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 callsSDKadminHost/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 · CallAppSettingBuilderCallAppSettings · CometChatCalls.isInitialized · login · loginWithAuthToken · logout · getLoggedInUserUser?
  • Session: CometChatCalls.generateCallToken(sessionId, …)CallToken · joinSession(…)Widget? · SessionSettingsBuilderSessionSettings · 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()) → CallLogRequestfetchNext/fetchPreviousCallLog (+ 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 appthere 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 SDKsleaveSession() 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 initinit 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
109
Forks
2
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
cometchat-flutter-v5-sdk
Source
github.com/cometchat/cometchat-skills