cometchat-flutter-v5-calls
CometChat Calls integration for Flutter UIKit v5 (GetX-based, cometchat_calls_uikit separate package). Covers package wiring (cometchat_calls_uikit ^5.0.15 alongside cometchat_chat_uikit ^5.2), CometChatCallingExtension via UIKitSettingsBuilder, CallNavigationContext.navigatorKey
What it does
Purpose
Production-grade voice + video calling for Flutter UIKit v5 (GetX-based). Loaded by cometchat-calls when framework === "flutter" and flutter_version === "v5". Operates in two modes:
- Standalone — calls is the product.
cometchat_chat_sdk(signaling) +cometchat_calls_sdk(WebRTC) without thecometchat_chat_uikitUI Kit. Custom call screens (or hand-roll the kit's call widgets without the conversation kit). VoIP push is mandatory — same rule as native iOS / Android. - Additive — calls layered onto an existing v5 chat integration. Adds
cometchat_calls_uikitpackage, configures the calling extension viaUIKitSettingsBuilder.callingExtension, setsCallNavigationContext.navigatorKeyonMaterialApp, mounts the global incoming-call listener at the app shell.
Read these other skills first:
cometchat-calls— dispatcher (modes, hard rules, anti-patterns)cometchat-flutter-v5-core— UIKitSettingsBuilder, init/login order, GetX scope rules, app entry conventionscometchat-flutter-v5-events— CometChatMessageEvents / CometChatCallEvents subscription patterns
Ground truth:
- SDK source —
~/Downloads/calls-sdk/calls-sdk-flutter-5/sdk/ - Sample app —
~/Downloads/calls-sdk/calls-sdk-flutter-5/sample-apps/ - Public docs — https://www.cometchat.com/docs/calls/flutter/overview
1. The seven hard rules — Flutter v5 specialization
1.0 Calls SDK login is its own step (v5+)
The v5 Calls SDK has its own auth state, separate from the Chat SDK. After CometChat.login succeeds, you MUST also call CometChatCalls.login — without it, the FIRST calls API call throws "auth token cannot be null".
import 'package:cometchat_sdk/cometchat_sdk.dart';
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
CometChat.login(uid, AUTH_KEY,
onSuccess: (User user) {
// Chat SDK ready — now login Calls SDK
CometChatCalls.login(
uid: uid,
authKey: AUTH_KEY,
onSuccess: (User? callUser) {
// both ready
},
onError: (CometChatException e) {
// surface to user
},
);
},
onError: (CometChatException e) {
// chat login failed
},
);
For production with server-minted tokens:
CometChatCalls.loginWithAuthToken(
authToken: tokenFromBackend,
onSuccess: (User? user) { /* … */ },
onError: (CometChatException e) { /* … */ },
);
Surprises:
- Chat SDK persists login via shared preferences across launches. The Calls SDK does NOT — always check
CometChatCalls.getLoggedInUser()on app start and re-login if it returns null. - The Calls SDK's onSuccess hands back
User?(nullable) — guard before using.
1.1 Dual-SDK contract — CometChatCallingExtension hides it (additive); standalone code sees both
In additive mode, the CometChatCallingExtension registered via UIKitSettingsBuilder.callingExtension wires both SDKs internally — your code rarely touches CometChatUIKitCalls directly. In standalone mode (no UI Kit), you call both SDKs explicitly:
// ✓ RIGHT — additive mode (calling extension owns the dual-SDK split)
final settings = (UIKitSettingsBuilder()
..appId = 'APP_ID'
..region = 'us'
..authKey = 'AUTH_KEY'
..subscriptionType = CometChatSubscriptionType.allUsers
..callingExtension = CometChatCallingExtension())
.build();
CometChatUIKit.init(uiKitSettings: settings);
// ✓ RIGHT — standalone mode (explicit dual-SDK)
import 'package:cometchat_chat_sdk/cometchat_chat_sdk.dart';
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
// Chat SDK — initiate ringing
final outgoing = Call(receiverUid, CometChatReceiverType.user, CometChatCallType.video);
final initiated = await CometChat.initiateCall(call: outgoing);
// Calls SDK — join WebRTC session after acceptance.
// Use SessionSettingsBuilder + callback shape — matches the upstream sample
// at calls-sdk-flutter-5/sample-apps/cometchat-calls-sample-app-flutter/.
// CallSettingsBuilder is the chat-side type; SessionSettingsBuilder is what
// session/joinSession accepts.
final sessionSettings = (SessionSettingsBuilder()
.setTitle('CometChat Call')
.startVideoPaused(false)
.startAudioMuted(false))
.build();
CometChatCalls.joinSession(
sessionId: sessionId,
sessionSettings: sessionSettings,
onSuccess: (Widget? widget) {
// render widget via SizedBox.expand; register SessionStatusListeners
},
onError: (CometChatCallsException err) {
debugPrint('joinSession failed: ${err.message}');
},
);
1.2 VoIP push — flutter_callkit_incoming + firebase_messaging + platform-channel bridges
Standalone mode requires working VoIP push end-to-end. The Flutter stack:
flutter_callkit_incoming— bridges CallKit (iOS) and a custom heads-up notification (Android). Single Dart API for "ring this device".firebase_messaging— FCM data messages on Android (priorityhigh,datapayload — NOTnotification).- iOS PushKit — Flutter doesn't have a first-party PushKit plugin; the skill ships a tiny platform-channel bridge in
ios/Runner/AppDelegate.swiftregisteringPKPushRegistry.voIPand forwarding payloads through aMethodChannelto Dart. - Server side — same split as native: PushKit (VoIP cert) for iOS, FCM data-message for Android.
In additive mode, this is opt-in but recommended.
1.3 Foreground service — Android 14+ rules apply unchanged
⚠️ Android build prerequisite — Jetifier is mandatory. One of the cometchat_calls_uikit transitive deps pulls in the legacy com.android.support:support-compat:26.1.0 AAR. Without Jetifier, AGP fails the assembleDebug step with dozens of Duplicate class android.support.v4.* errors against androidx.core. Set in android/gradle.properties:
android.useAndroidX=true
android.enableJetifier=true
Flutter 3.x scaffolds omit enableJetifier=true by default — the build blows up on the first flutter build apk if you skip this. The error is loud but the fix is one line.
The cometchat_calls_uikit registers CometChatOngoingCallService via manifest merge, but the host app's android/app/src/main/AndroidManifest.xml must declare the four FOREGROUND_SERVICE permissions plus MANAGE_OWN_CALLS / BIND_TELECOM_CONNECTION_SERVICE — the same rule as native Android (cf. cometchat-android-v5-calls rule 1.3).
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
tools:ignore="ProtectedPermissions" />
Silent crash on Android 14+ if FOREGROUND_SERVICE_PHONE_CALL is missing. The tools namespace must be declared on the <manifest> element.
1.4 Server-minted auth tokens
cometchat-flutter-v5-production covers the token-endpoint pattern. Production calls path uses CometChatUIKit.loginWithAuthToken(token), not loginWithAuthKey(uid, authKey). The Calls SDK piggybacks on the Chat SDK auth context — there is no separate calls-only token.
1.5 Hangup cleanup — Dart + native + system call UI
Future<void> endCall(String sessionId) async {
// 1. End the Calls SDK session — releases WebRTC tracks.
// CometChatCalls.endSession() does NOT exist on Flutter — use the
// CallSession singleton's leaveSession().
await CallSession.getInstance().leaveSession();
await CometChatOngoingCallService.abort();
// 2. Tell the OS-level call UI the call ended
await FlutterCallkitIncoming.endAllCalls();
// 3. Pop the call screen
if (mounted) {
Navigator.of(context, rootNavigator: true).popUntil((r) => r.isFirst);
}
// 4. After logout flows: reset ServiceLocator if it was used directly
// (UIKit handles this for additive mode)
}
Skipping FlutterCallkitIncoming.endAllCalls leaves the lock-screen/heads-up call UI stuck. Skipping the Navigator.popUntil strands the Dart-side call screen with WebRTC views still in the tree.
1.6 Permissions — permission_handler + Info.plist + AndroidManifest
import 'package:permission_handler/permission_handler.dart';
await [Permission.camera, Permission.microphone, Permission.notification].request();
iOS — ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>So you can be seen during video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>So you can talk during voice and video calls.</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>voip</string>
<string>remote-notification</string>
</array>
Android — manifest as above (rule 1.3) plus runtime requests via permission_handler.
1.7 IncomingCall mounted at app root + CallNavigationContext.navigatorKey
V5 uses GetX's navigator key pattern. CallNavigationContext.navigatorKey MUST be set on MaterialApp.navigatorKey so call overlays can navigate even when a call is initiated from a sub-route:
// ✓ RIGHT
MaterialApp(
navigatorKey: CallNavigationContext.navigatorKey,
// ...
);
Global call listener registration (incoming calls fire app-wide) belongs in the app shell's State, not in a feature screen:
class AppShellState extends State<AppShell>
with CallListener, CometChatCallEventListener {
static const _listenerId = 'app-shell-call-listener';
@override
void initState() {
super.initState();
CometChat.addCallListener(_listenerId, this);
CometChatCallEvents.addCallEventsListener(_listenerId, this);
}
@override
void dispose() {
CometChat.removeCallListener(_listenerId);
CometChatCallEvents.removeCallEventsListener(_listenerId);
super.dispose();
}
@override
void onIncomingCallReceived(Call call) {
// Route through CallNavigationContext or flutter_callkit_incoming
}
}
Use a stable string ID for the listener — duplicate IDs overwrite, distinct IDs fire both (double-ring bug).
2. Setup
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
cometchat_chat_uikit: ^5.2.14 # additive mode — already there
cometchat_calls_uikit: ^5.0.15 # adds the calling extension + widgets
permission_handler: ^11.0.0 # rule 1.6
flutter_callkit_incoming: ^2.0.0 # standalone — VoIP UI bridge
firebase_messaging: ^14.0.0 # standalone — Android FCM
firebase_core: ^2.0.0 # firebase_messaging peer
Hosted source if pub.dev resolution lags:
cometchat_calls_uikit:
hosted: https://dart.cloudsmith.io/cometchat/cometchat/
version: ^5.0.15
Init order — calling extension flows the dual-SDK init internally:
final settings = (UIKitSettingsBuilder()
..appId = CometChatConfig.appId
..region = CometChatConfig.region
..authKey = CometChatConfig.authKey
..subscriptionType = CometChatSubscriptionType.allUsers
..callingExtension = CometChatCallingExtension()) // ← rule 1.1 (additive)
.build();
await CometChatUIKit.init(uiKitSettings: settings);
In standalone mode (no UI Kit), use the SDKs directly:
await CometChat.init(CometChatConfig.appId, AppSettings.builder
..setRegion(CometChatConfig.region)
..subscribePresenceForAllUsers());
await CometChatCalls.init(CallAppSettings.builder
..setAppId(CometChatConfig.appId)
..setRegion(CometChatConfig.region));
3. Components catalog (UI Kit widgets)
Imports (additive mode — barrel re-exports cometchat_uikit_shared, cometchat_sdk, cometchat_calls_sdk):
import 'package:cometchat_calls_uikit/cometchat_calls_uikit.dart';
| Widget | Purpose |
|---|---|
CometChatCallButtons(user: ..., group: ...) | Voice + video buttons. Drop into CometChatMessageHeader trailing slot or anywhere in the tree. Mutually exclusive user / group. |
CometChatIncomingCall(call:, onAccept:, onDecline:) | Foreground in-app ring UI (additive mode). Callbacks take (BuildContext, Call), NOT (Call) alone — common mistake. |
CometChatOutgoingCall(call:, user: OR group:, onCancelled:) | Dialing UI. call: is the result of CometChatUIKitCalls.initiateCall(...). Callbacks (BuildContext, Call)?. |
CometChatOngoingCall(callSettingsBuilder:, sessionId:) | Active call view, hosts WebRTC. |
CometChatCallLogs(onItemClick:, callLogsRequestBuilder:) | Paginated history. CometChatCallLogDetails is NOT a UIKit export — copy the sample-app pattern from ~/Downloads/calls-sdk/calls-sdk-flutter-5/sample-apps/.../call_log_details/ for the detail screen. |
CometChatUIKitCalls API:
| Method | Purpose |
|---|---|
CometChatUIKitCalls.initiateCall(call) | Start a call (wraps Chat SDK initiateCall + Calls SDK preflight) |
CometChatUIKitCalls.acceptCall(sessionId) | Accept incoming |
CometChatUIKitCalls.rejectCall(sessionId, status) | Reject / cancel |
CometChatUIKitCalls.generateToken(sessionId) | Mint session-scoped RTC token |
CometChatUIKitCalls.startSession(sessionId, settings) | Start WebRTC |
CometChatUIKitCalls.endSession() | End and cleanup |
4. Standalone integration
When product === "voice-video" and there is no v5 chat integration.
Split by calling mode:
4a. Standalone — Session mode (meeting-room UX, no ringing)
Calls SDK ONLY. NO Chat SDK. Matches ~/Downloads/calls-sdk/calls-sdk-flutter-5/sample-apps/cometchat-calls-sample-app-flutter/. Scaffold:
pubspec.yaml—cometchat_calls_sdk: ^5.0.0ONLY. Nocometchat_chat_sdk/cometchat_uikit_chat.lib/main.dart—CometChatCalls.init(appId, region, authKey)+permission_handlerflow.lib/screens/call_screen.dart—StatefulWidget implements SessionStatusListeners, ButtonClickListeners.CometChatCalls.joinSession(sessionId:, sessionSettings: SessionSettingsBuilder().build(), onSuccess:, onError:).CometChatOngoingCallService.launch/abort. Seereferences/call-session.md.- Native config — Camera + microphone permissions only (no PushKit, no FCM for VoIP).
Why no Chat SDK / no VoIP push: session mode never touches a Chat SDK call entity. No ringing.
4b. Standalone — Ringing mode (CallKit + FCM + UIKit-driven UI)
Dual-SDK: Chat SDK signaling + Calls SDK media. Scaffold:
lib/main.dart— Chat SDK + Calls SDK init (no UIKit). Permission_handler request flow. PushKit + FCM listener registration.lib/services/voip_service.dart— Combinesflutter_callkit_incomingevents +firebase_messaging+ iOS platform-channel PushKit. On payload →FlutterCallkitIncoming.showCallkitIncoming(...). On accept → navigate to ongoing-call screen.lib/widgets/call_button.dart— Voice + video icon buttons rendered next to a user / contact.lib/screens/ongoing_call_screen.dart—CometChatCalls.joinSession(sessionId:, sessionSettings:, onSuccess:, onError:)with the returned Widget rendered viaSizedBox.expand. Implements rule 1.5 cleanup. SetsresizeToAvoidBottomInset: false.lib/screens/call_logs_screen.dart—/callsroute. Paginated viaCallLogsRequestBuilder.MaterialApp.navigatorKey: CallNavigationContext.navigatorKey— rule 1.7.- Native config —
Info.plist(rule 1.6),AndroidManifest.xml(rule 1.3 + FCM service registration), Firebase config (google-services.jsoninandroid/app/,GoogleService-Info.plistinios/Runner/).
5. Additive integration
When chat is already integrated. The skill:
- Adds
cometchat_calls_uikittopubspec.yaml. - Patches the existing
UIKitSettingsBuilderto add..callingExtension = CometChatCallingExtension(). - Sets
CallNavigationContext.navigatorKeyonMaterialApp.navigatorKey(rule 1.7). - Mounts the global call listener in the app-shell
State(rule 1.7). - Confirms
CometChatMessageHeaderalready shows call buttons (auto-rendered whenuser/groupis passed). - Optionally adds
CometChatCallLogsas a tab/screen if user picked dedicated history. - VoIP push: opt-in (substantial native config).
6. Anti-patterns
MaterialAppwithoutCallNavigationContext.navigatorKey. Call overlays can't navigate; in-app ring works but accept-into-ongoing breaks. Rule 1.7.- Forgetting
..callingExtension = CometChatCallingExtension()onUIKitSettingsBuilder. Calls compile butCometChatMessageHeaderdoesn't show call icons; nothing rings. Most common additive-mode mistake. onAccept/onDeclinetyped asFunction(Call)instead ofFunction(BuildContext, Call). Compile errors are obvious; the dangerous case is when an agent wraps an existing chat screen's accept handler and silently mismatches signatures.- Per-screen incoming-call handling. Calls only ring on the screen where the listener is attached. Listener belongs in the app shell (rule 1.7).
- Skipping
resizeToAvoidBottomInset: falseon Scaffolds with call UI. Keyboard-show during a call resizes WebRTC views and breaks layout. - Mixing
cometchat_chat_uikit ^5.xwithcometchat_calls_uikit ^4.x(or vice versa). Internal SDK pin clash. Both UI Kit packages must be on matching majors. - Sending Android push as
notificationinstead ofdata. ConnectionService can't interceptnotificationpayloads. Server must senddata: { type: "incoming_call", sessionId: ... }withpriority: "high".
7. Verification checklist
Static:
-
cometchat_calls_uikit ^5.0.15inpubspec.yaml(additive) ORcometchat_calls_sdk ^4.2.2+cometchat_sdk ^4.1.2(standalone) -
..callingExtension = CometChatCallingExtension()onUIKitSettingsBuilder(additive) -
MaterialApp.navigatorKeyisCallNavigationContext.navigatorKey - Global call listener attached in app-shell State, removed in
dispose - Listener uses a stable string ID
- Camera + microphone + notification permissions requested via
permission_handler - iOS
Info.plist: NSCameraUsageDescription + NSMicrophoneUsageDescription + UIBackgroundModes (audio + voip + remote-notification) - Android manifest: four FOREGROUND_SERVICE_* permissions + MANAGE_OWN_CALLS + BIND_TELECOM_CONNECTION_SERVICE
- Hangup path:
endSession()+FlutterCallkitIncoming.endAllCalls()+Navigator.popUntil(rule 1.5) - Standalone only: Firebase configured (
google-services.json+GoogleService-Info.plist) - Standalone only:
flutter_callkit_incoming+firebase_messaging+ iOS PushKit platform-channel bridge
Runtime (real devices, both platforms):
- iOS — terminated app, lock-screen rings on incoming call
- iOS — answer from lock screen → opens app, joins ongoing call
- Android — terminated app, heads-up notification rings on incoming call
- Android — answer from heads-up → opens app, joins ongoing call
- Both — outgoing call connects, two-way audio + video
- Both — hangup releases camera + mic, no system call UI stuck
- Android 14+: ongoing-call notification visible, swipe-up doesn't kill the call
- Keyboard-show during call doesn't break WebRTC view (rule from anti-pattern 5)
8. Pointers
cometchat-calls— dispatchercometchat-flutter-v5-core— UIKitSettingsBuilder, init/login order, GetX scopecometchat-flutter-v5-events— CometChatCallEvents subscription patternscometchat-flutter-v5-push— FCM/APNs for chat (overlap with VoIP push but distinct paths)cometchat-flutter-v5-production— server-minted tokens, ProGuard, environment configcometchat-flutter-v5-troubleshooting— pubspec conflicts, GetX issues, Pod errors, runtime crashescometchat-flutter-v6-calls+cometchat-flutter-v6-migration— when migrating to V6 (calls fold into the unified package, Bloc replaces GetX)
Capabilities
Install
Quality
deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (20,102 chars)