cometchat_voip turns a CometChat call push into a ringing phone: a call notification with Answer and Decline, a full-screen ring screen over the lock screen (CallKit on iOS), the ringtone, a ring timeout, missed calls and busy handling, whether your app is in the foreground, in the background or killed.
This page covers the push side of ringing. Starting, answering and ending calls with the Calls SDK is on the Ringing page.
Prerequisites
- The Calls SDK set up and the user logged in.
- Push notifications enabled in the CometChat dashboard, with the push providers, Firebase project and Apple credentials set up. See Getting Started. Keep each provider’s Provider ID at hand.
- Android: Firebase set up in your app with
firebase_coreandfirebase_messaging. Call pushes reach your app throughfirebase_messaging. - iOS: an APNs provider that uses a token-based
.p8key; a certificate provider serves only one of chat and VoIP pushes. Its Bundle ID must be your app’s, and its production setting must match the build: a build run from Xcode gets a sandbox token. Test on a real device; CallKit and VoIP pushes don’t run in the simulator.
Step 1: Add the packages
cometchat_voip brings cometchat_calls_sdk with it; list both so the Calls SDK version is yours to pin. The Calls SDK’s own requirements on both platforms are on Setup.
- Android
- iOS
Set
minSdk 26 in android/app/build.gradle. cometchat_voip adds what ringing needs to your manifest: its ring screen, its ringing foreground service (type phoneCall) and the permissions for them, including USE_FULL_SCREEN_INTENT and FOREGROUND_SERVICE_PHONE_CALL, which Google Play asks you to justify.It doesn’t add the Calls SDK’s ongoing call service. Your app declares that one, as described in Background Handling, or the microphone and camera stop when the answered call goes to the background.Step 2: Initialize
Initialize the Calls SDK and thencometchat_voip early in main, on every launch: a push that launched a killed app waits for Dart before the Calls SDK can connect the ring, and iOS doesn’t wait for long. Build the configuration from the same CallAppSettings you give the Calls SDK, so the app ID and region match.
Step 3: Hand call pushes to cometchat_voip
On Android, pass the data of every push your app receives tohandlePushNotification, from both your foreground and your background message handlers. It returns true for a call push, which cometchat_voip then rings. Other pushes, such as chat messages, are left to your own handling.
auto modes. cometchat_voip saved its configuration when init last ran, and the native side rings from that; if it needs the Calls SDK, for example to send a decline after the app was killed, it starts its own isolate. CometChatVoip.init must have run once on the device, and a call can’t ring before that. In manual mode, where the ring is yours, initialize the Calls SDK and cometchat_voip in the handler first.
On iOS there’s nothing to hand over: VoIP pushes go straight to cometchat_voip. If your app also has chat notifications, see If your app also rings calls.
Step 4: Register the push token
iOS: in both
auto modes cometchat_voip owns the PKPushRegistry: it obtains the PushKit VoIP token during init and registers it as soon as a user is logged in to the Calls SDK, at init if one already is, otherwise at the next login. There’s nothing for you to pass.
Android: cometchat_voip can’t see the FCM token, so hand it over. It keeps the token in memory only, so hand it over on every launch, not just the first: unregisterPushToken and verifyPushSetup need it in this run of the app. unregisterPushToken throws a noPushToken error if it hasn’t been given one.
autoRegisterPushToken on (the default), a later Calls SDK login in the same run registers the token again. A chat token registered with cometchat_push_notifications can be the same FCM token; CometChat keeps one entry per token.
Step 5: Permissions and ring readiness
Microphone and camera.cometchat_voip asks on Answer, with requestCallPermissions on (the default), and the call joins whatever the user chose. The timing on each platform, and what happens on a locked phone, is under Answer a call. A screen of your own can check first with permissionsToAnswer(call) and ask with requestCallPermissions(call). Nothing asks for the caller: request those permissions before initiateCall.
Ring readiness. Some device settings silence a call with no error anywhere. Check once after login and tell the user:
CometChatVoip.verifyPushSetup() checks the rest of the setup and reports ok, notInitialized, notLoggedIn, tokenNotRegistered, tokenRegistrationFailed, notificationsBlocked or ringNotReady. CometChatVoip.getDiagnostics() returns a snapshot that is safe to attach to a bug report: its pushesSeen, pushesRung and pushesDropped counts tell “no push arrived” from “a push arrived and was dropped”.
Receive a call
How an incoming call rings depends oncallMode:
In every mode, when the user answers, your app joins the call (see Answer a call).
Auto
cometchat_voip does all of the ringing, including its own ring screen. Listen for an answer, and for a call that stops ringing:
Your own ring screen
WithcallMode: CometChatCallMode.autoCustomScreen, the full-screen alert and a tap on the notification open your Flutter ring screen instead of cometchat_voip’s. The notification, its ringtone, its Answer and Decline buttons and the ring timeout are still cometchat_voip’s. Android only.
answer asks for the microphone (and the camera for a video call) by itself. A call that arrives while your app is in the foreground shows a notification instead of the full-screen alert; to show your ring screen then too, also listen to CometChatVoip.onCallRinging.
Manual
WithcallMode: CometChatCallMode.manual, cometchat_voip only reads the push: it drops duplicates, expired calls, calls this user placed and calls meant for someone else, and passes every new call to onCallRinging. Every other step is yours. ringSurfaces is Android only and throws on iOS, where reporting a call to CallKit is the ring.
CometChatVoip.accept(sessionId) (fires onCallAnswered) or CometChatVoip.decline(sessionId). accept reports the answer without touching a ring; in the auto modes use answer, which also stops it. For your own ring UI without showIncomingNotification, keep the app alive with ringSurfaces.keepAlive(call), stop at call.expiresAt and show a missed call with ringSurfaces.showMissedCall(call). A second call while one is ringing or live is yours to refuse with decline(sessionId, busy: true). In Manual mode, decline retries a few times and then throws; it doesn’t survive the app being killed.
Theming the ring screen
On Android, the ring screencometchat_voip draws in Auto mode, and its ring, connecting and missed call notifications, take each colour from, in order:
- a
CometChatVoipIncomingCallThemeset in code; - the
cometchat_voip_call_*colour resources, which your app can override by name; cometchat_voip’s defaults.
In code
- Mode.
systemfollows the phone’s light or dark setting when the call rings. If your app has its own light and dark switch, passlightordark, and set the theme again when the user switches. - Saved. The theme is saved when you pass it, so it also applies when a push starts your app from killed.
- Kept across
init. Aninitwithout a theme, such as the one in your background message handler, keeps the saved theme. Remove it withCometChatVoip.setIncomingCallTheme(null). - Last one wins. Between
incomingCallThemeandsetIncomingCallTheme, the theme set last is used. - Only what you set. A colour you leave out comes from its resource. In a light set, it comes from built-in light colours instead, so a light page never gets the dark default’s light text.
- Where it applies. In Auto mode, the ring screen and the notifications. With your own ring screen, the screen is yours, so only the notifications. On iOS, CallKit draws the call and the theme does nothing.
As resources
Override any of these by name in your app’sandroid/app/src/main/res/values/colors.xml, and in res/values-night/colors.xml for dark mode. Your app’s resource wins over cometchat_voip’s. Each one matches a field of CometChatVoipIncomingCallColors.
android/app/src/main/res/values/colors.xml
Incoming call events
Listen tocometchat_voip’s events: onCallAnswered to join, onCallEnded to close. The Calls SDK’s IncomingCallListener (CometChatCalls.addIncomingCallListener) reports the same rings and ends if you prefer it, and the caller uses it to learn that a call was declined. CometChatVoip.onCallRinging fires for a call that starts ringing, in every mode. Don’t use the listener id cometchat_voip: the package registers under it, and ids are keys, so a second listener with that id replaces the first.
How a ring ends
Every way out is handled bycometchat_voip and needs no code in your app.
The caller’s column depends on a push reaching the caller; see Listen for calls ending.
CometChatCallEndReason.fromWire(reason) turns a status into the same values onCallEnded reports, if you want one vocabulary. It maps both busy and rejected to declined, so compare the status itself to tell them apart.
A call while in a call
Call waiting, the way the phone app does it. “In a call” means the Calls SDK is joined to a session.
In Manual mode, your app decides how to handle these calls.
Calls this device places (iOS)
A call this device places is on CallKit too, so it survives a cellular call, can be ended from the lock screen and appears in Recents.cometchat_voip reports a session the Calls SDK owns by itself, under your app’s name until a participant connects. Report it yourself to give CallKit the callee’s name and the call type from the start:
Reporting the session
cometchat_voip follows the Calls SDK’s session and media events itself, so CallKit’s timer, mute and hold follow the call without you doing anything. These calls are optional, for a session it never saw ring:
reason is what CallKit records for the call: a CometChatCallEndReason, not a call status, so there is no ended. Android ignores it.
A screen that already answers calls
If your app answers and declines through CometChat from its own screen, such as an overlay driven by the Chat SDK’s calling, stopcometchat_voip’s ring without sending anything a second time:
Missed calls
WithshowMissedCallNotification on (the default), a missed call is shown when nobody answers in the ring time, or when the caller cancels before an answer. It isn’t shown when the call is answered here or on another device, or declined here. On onCallEnded, ended.reason.isMissed is true for these calls.
When the app is killed or the phone is locked
- The call push wakes the app: a high-priority FCM push runs your background message handler on Android; a VoIP push wakes
cometchat_voipon iOS. - On Android, the handler hands over the push (Step 3).
cometchat_voiprings: on Android on a foreground service that keeps the app alive through the answer, with the ring screen over the lock screen; on iOS through CallKit.- On Answer, Android asks for any missing microphone and camera permission and opens your app over the lock screen; iOS answers in CallKit and asks once the app is active.
onCallAnsweredfires as soon as your code listens. Join the call there, without waiting for your UI. - A decline or a timeout is sent from a queue, so it goes out even if the app is killed right after the tap.
cometchat_voip, and subscribe to onCallAnswered, at app start, not only after a screen opens, so an answered call finds a ready SDK. The Calls SDK restores its login from disk.
Logout
Before logging the user out, callCometChatVoip.unregisterPushToken() while the session that owns the token still works, then log out of the Calls SDK with CometChatCalls.logout. Logging out stops a call still ringing for that user.
Troubleshooting
Related
- Ringing: start, answer, decline, cancel and end calls with the Calls SDK.
- Migrating from Legacy Ringing.
- Background Handling: keep a joined call running when the app goes to the background.