Skip to content
10 changes: 7 additions & 3 deletions calls/flutter/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
await CallSession.getInstance()?.muteAudio();
```

### Unmute Audio

Check warning on line 32 in calls/flutter/actions.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/actions.mdx#L32

Did you really mean 'Unmute'?

Unmutes your local microphone, resuming audio transmission.

Check warning on line 34 in calls/flutter/actions.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/actions.mdx#L34

Did you really mean 'Unmutes'?

```dart
await CallSession.getInstance()?.unMuteAudio();
Expand Down Expand Up @@ -219,12 +219,16 @@

### Leave Session

Ends your participation and disconnects gracefully. The call continues for other participants.
Ends your participation and disconnects gracefully. In a 1:1 call (`setPeerCall(true)`) this is the hang-up: the other side's session ends too. In a group call the session continues for the other participants.

```dart
await CallSession.getInstance()?.leaveSession();
await CallSession.getInstance()?.leave();
```

<Note>
`leaveSession()` and `endCallSession()` are deprecated since 5.1; use `leave()`. To end a call that is still ringing, use `CometChatCalls.endCall` with `cancelled`, as described in [Ringing](/calls/flutter/ringing#cancel-a-call); `leave()` alone doesn't tell the other side why.
</Note>

### Raise Hand

Shows a hand-raised indicator to get attention from other participants.
Expand All @@ -247,7 +251,7 @@

### Toggle Mute Audio

Toggles between muted and unmuted audio.

Check warning on line 254 in calls/flutter/actions.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/actions.mdx#L254

Did you really mean 'unmuted'?

```dart
await CallSession.getInstance()?.toggleMuteAudio();
Expand Down Expand Up @@ -283,7 +287,7 @@

### End Call Session

Alias for `leaveSession()`. Ends your participation and disconnects gracefully.
Deprecated alias for `leave()`. Ends your participation and disconnects gracefully.

```dart
await CallSession.getInstance()?.endCallSession();
Expand Down Expand Up @@ -341,7 +345,7 @@
await CallSession.getInstance()?.openVirtualBackgroundSettingsPanel();
```

## State Getters

Check warning on line 348 in calls/flutter/actions.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/actions.mdx#L348

Did you really mean 'Getters'?

Read-only properties on `CallSession` that return the current state of the local user in the call.

Expand Down
180 changes: 58 additions & 122 deletions calls/flutter/background-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,24 @@ flowchart LR
| User enters Picture-in-Picture | [PiP Mode](/calls/flutter/picture-in-picture) handles this |
| User presses HOME during call | **Use CometChatOngoingCallService** |
| User switches to another app | **Use CometChatOngoingCallService** |
| Receiving calls when app is killed | [VoIP Calling](/calls/flutter/voip-calling) handles this |
| Receiving calls when app is killed | [Push Ringing](/calls/flutter/voip-calling) keeps the app alive from the ring until the answered call joins; from then on, use **CometChatOngoingCallService** |

<Note>
Background Handling is different from [VoIP Calling](/calls/flutter/voip-calling). VoIP handles **receiving** calls when the app is not running. Background Handling keeps an **active** call alive when the user leaves the app.
Background Handling is different from [Push Ringing](/calls/flutter/voip-calling). They cover different phases of a call on Android:

| Phase | What keeps the app alive |
|-------|--------------------------|
| Ringing: the push arrives, until Answer | `cometchat_voip`'s ringing service |
| Answering: Answer tapped, until the session is joined | The same service, as "Connecting call…" |
| In the call: joined, and the user presses Home or switches apps | **`CometChatOngoingCallService`, which your app starts** |

`cometchat_voip` stops its service once the session is joined, so a joined call needs this page whether it was rung through push or not. It applies to every call: ringing calls, direct calls, group calls and meetings.
</Note>

<Tip>
**Using the CometChat UI Kit?** Its call screen starts `CometChatOngoingCallService` when the call joins and stops it when the call ends. There's nothing to add.
</Tip>

<Warning>
`CometChatOngoingCallService` starts an Android foreground service with a persistent notification showing mute/unmute and hang up actions. On iOS, this is a no-op since iOS uses CallKit for ongoing call notifications.
</Warning>
Expand All @@ -53,70 +65,52 @@ Background Handling is different from [VoIP Calling](/calls/flutter/voip-calling

## Implementation

### Step 1: Add Manifest Permissions (Android)
### Step 1: Permissions (Android)

Add the required permissions to your `android/app/src/main/AndroidManifest.xml`:
The Calls SDK declares the foreground service permissions it needs (`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_MICROPHONE` and `FOREGROUND_SERVICE_CAMERA`). On Android 13 and later the notification also needs `POST_NOTIFICATIONS`; declare it in `android/app/src/main/AndroidManifest.xml` and request it at runtime (`cometchat_voip` already declares and requests it if your app uses it):

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Required for foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

</manifest>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
```

### Step 2: Start Service on Session Join
### Step 2: Start the service on join, stop it on leave

Start the ongoing call service when the user successfully joins a call session:
Start the ongoing call service when the user joins the call session, and stop it when the call ends. Pass `isVideo: false` for an audio call, so the service doesn't use the camera.

```dart
class _SessionStatusListener extends SessionStatusListeners {
@override
void onSessionJoined() {
// Start the foreground service to keep call alive in background
CometChatOngoingCallService.launch();
}
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
import 'package:flutter/material.dart';

@override
void onSessionLeft() {
// Stop the service when call ends
CometChatOngoingCallService.abort();
if (mounted) Navigator.of(context).pop();
}
class CallScreen extends StatefulWidget {
final String sessionId;
final bool isVideo;

const CallScreen({super.key, required this.sessionId, required this.isVideo});

@override
void onConnectionClosed() {
CometChatOngoingCallService.abort();
}
State<CallScreen> createState() => _CallScreenState();
}

import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';

class _CallScreenState extends State<CallScreen> {
Widget? callWidget;

late final SessionStatusListeners _sessionListener;
late final CallScreenSessionListener _sessionListener;

@override
void initState() {
super.initState();
_setupSessionListener();
_joinCall();
}

void _setupSessionListener() {
_sessionListener = _SessionStatusListener();
_sessionListener = CallScreenSessionListener(
isVideo: widget.isVideo,
onCallEnded: () {
if (mounted) Navigator.of(context).pop();
},
);
CallSession.getInstance()?.addSessionStatusListener(_sessionListener);
_joinCall();
}

void _joinCall() {
final sessionSettings = (SessionSettingsBuilder()
..setTitle("My Call"))
..setType(widget.isVideo ? SessionType.video : SessionType.audio))
.build();

CometChatCalls.joinSession(
Expand All @@ -127,14 +121,14 @@ class _CallScreenState extends State<CallScreen> {
},
onError: (CometChatCallsException e) {
debugPrint("Failed to join: ${e.message}");
Navigator.of(context).pop();
if (mounted) Navigator.of(context).pop();
},
);
}

@override
void dispose() {
// Always stop the service when screen is disposed
// Always stop the service when the call screen goes away
CometChatOngoingCallService.abort();
CallSession.getInstance()?.removeSessionStatusListener(_sessionListener);
super.dispose();
Expand All @@ -147,97 +141,39 @@ class _CallScreenState extends State<CallScreen> {
);
}
}
```

<Note>
Flutter listeners are not lifecycle-aware. You must manually remove listeners in `dispose()` and call `CometChatOngoingCallService.abort()` to clean up.
</Note>

---
class CallScreenSessionListener extends SessionStatusListeners {
final bool isVideo;
final VoidCallback onCallEnded;

## Complete Example
CallScreenSessionListener({required this.isVideo, required this.onCallEnded});

```dart
class _SessionStatusListener2 extends SessionStatusListeners {
@override
void onSessionJoined() {
// Start foreground service (Android only, no-op on iOS)
CometChatOngoingCallService.launch();
}

@override
void onSessionLeft() {
CometChatOngoingCallService.abort();
if (mounted) Navigator.of(context).pop();
}

@override
void onConnectionClosed() {
CometChatOngoingCallService.abort();
}
}

import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
import 'package:flutter/material.dart';

class CallScreen extends StatefulWidget {
final String sessionId;

const CallScreen({super.key, required this.sessionId});

@override
State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
Widget? callWidget;

@override
void initState() {
super.initState();
_setupSessionListener();
_joinCall();
}

void _setupSessionListener() {
_sessionListener = _SessionStatusListener2();
CallSession.getInstance()?.addSessionStatusListener(_sessionListener);
}

void _joinCall() {
final sessionSettings = (SessionSettingsBuilder()
..setTitle("My Call"))
.build();

CometChatCalls.joinSession(
sessionId: widget.sessionId,
sessionSettings: sessionSettings,
onSuccess: (Widget? widget) {
setState(() => callWidget = widget);
},
onError: (CometChatCallsException e) {
debugPrint("Failed to join: ${e.message}");
if (mounted) Navigator.of(context).pop();
},
);
// Start the foreground service (Android only, no-op on iOS)
CometChatOngoingCallService.launch(isVideo: isVideo);
}

@override
void dispose() {
void onSessionLeft() {
CometChatOngoingCallService.abort();
CallSession.getInstance()?.removeSessionStatusListener(_sessionListener);
super.dispose();
onCallEnded();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: callWidget ?? const Center(child: CircularProgressIndicator()),
);
void onConnectionClosed() {
CometChatOngoingCallService.abort();
onCallEnded();
}
}
```

The service and its notification stay for the whole call, whether your app is in the foreground or not, until you call `abort()`.

<Note>
Flutter listeners are not lifecycle-aware. You must manually remove listeners in `dispose()` and call `CometChatOngoingCallService.abort()` to clean up.
</Note>

---

## API Reference
Expand All @@ -246,7 +182,7 @@ class _CallScreenState extends State<CallScreen> {

| Method | Description |
|--------|-------------|
| `launch()` | Starts the Android foreground service with an ongoing notification. No-op on iOS. |
| `launch({bool? isVideo})` | Starts the Android foreground service with an ongoing notification. Pass `isVideo: false` for an audio call; when omitted, the service reads the type from the session. No-op on iOS. |
| `abort()` | Stops the foreground service and removes the notification. No-op on iOS. |

<Note>
Expand All @@ -258,5 +194,5 @@ On iOS, ongoing call state is managed by CallKit automatically. `CometChatOngoin
## Related Documentation

- [Picture-in-Picture](/calls/flutter/picture-in-picture) - Keep call visible while using other apps
- [VoIP Calling](/calls/flutter/voip-calling) - Receive calls when app is killed
- [Session Status Listener](/calls/flutter/session-status-listener) - Listen for session events
- [Push Ringing](/calls/flutter/voip-calling) - Ring calls when the app is in the background or killed, until the answered call joins
- [Events](/calls/flutter/events) - Listen for session events
19 changes: 14 additions & 5 deletions calls/flutter/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
description: "Handle CometChat Calls SDK v5 events on Flutter for call lifecycle, participants, media state, errors, and real-time updates."
---

Handle call session events to build responsive UIs. The SDK provides five event listener interfaces to monitor session status, participant activities, media changes, button clicks, and layout changes. Unlike Android, Flutter listeners are not lifecycle-aware and must be manually removed in `dispose()` to prevent memory leaks.

Check warning on line 8 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L8

Did you really mean 'UIs'?

## Get CallSession Instance

Expand All @@ -15,8 +15,10 @@
CallSession? callSession = CallSession.getInstance();
```

The session exists as soon as it is **prepared**, before anyone joins: `CometChatCalls.initiateCall` prepares it on the caller, and `cometchat_voip` prepares it on the callee while the phone rings. That is what lets a caller listen for "ringing" and "answered" without joining (see [Ringing](/calls/flutter/ringing)).

Check warning on line 18 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L18

Did you really mean 'callee'?

<Warning>
Flutter listeners are **not** lifecycle-aware. You must manually remove all listeners in your widget's `dispose()` method to prevent memory leaks. Failing to do so may cause callbacks to fire on disposed widgets.

Check warning on line 21 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L21

Did you really mean 'widget's'?
</Warning>

---
Expand Down Expand Up @@ -66,11 +68,11 @@
| Event | Description |
|-------|-------------|
| `onSessionJoined` | Successfully connected and joined the session |
| `onSessionLeft` | Left the session via `leaveSession()` or was removed |
| `onSessionTimedOut` | Session ended due to inactivity timeout |
| `onSessionLeft` | Left the session via `leave()` or was removed |
| `onSessionTimedOut` | Session ended due to inactivity timeout. On a prepared session: nobody joined in time |
| `onConnectionLost` | Network interrupted, SDK attempting reconnection |
| `onConnectionRestored` | Connection restored after being lost |
| `onConnectionClosed` | Connection permanently closed, cannot reconnect |
| `onConnectionClosed` | Connection permanently closed, cannot reconnect. On a prepared session: the call ended before anyone joined; the reason arrives through `onIncomingCallEnded` |

Remove the listener when no longer needed:

Expand All @@ -90,9 +92,15 @@

```dart
class _ParticipantEventListener extends ParticipantEventListeners {
@override
void onParticipantConnected(Participant participant) {
// A participant's phone prepared the session but hasn't joined yet.
// On an outgoing call: the callee's phone is ringing.
}

@override
void onParticipantJoined(Participant participant) {
// A participant joined the call
// A participant joined the call. On an outgoing call: the callee answered.
}

@override
Expand All @@ -115,11 +123,12 @@

| Event | Parameter | Description |
|-------|-----------|-------------|
| `onParticipantJoined` | `Participant` | A participant connected to the call |
| `onParticipantConnected` | `Participant` | A participant's phone prepared the session but hasn't joined. On an outgoing call: the callee's phone is ringing |

Check warning on line 126 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L126

Did you really mean 'callee's'?
| `onParticipantJoined` | `Participant` | A participant joined the call. On an outgoing call: the callee answered |

Check warning on line 127 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L127

Did you really mean 'callee'?
| `onParticipantLeft` | `Participant` | A participant disconnected from the call |
| `onParticipantListChanged` | `List<Participant>` | Participant list updated |
| `onParticipantAudioMuted` | `Participant` | A participant muted their microphone |
| `onParticipantAudioUnmuted` | `Participant` | A participant unmuted their microphone |

Check warning on line 131 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L131

Did you really mean 'unmuted'?
| `onParticipantVideoPaused` | `Participant` | A participant turned off their camera |
| `onParticipantVideoResumed` | `Participant` | A participant turned on their camera |
| `onParticipantHandRaised` | `Participant` | A participant raised their hand |
Expand Down Expand Up @@ -207,7 +216,7 @@
| Event | Parameter | Description |
|-------|-----------|-------------|
| `onAudioMuted` | - | Your microphone was muted |
| `onAudioUnMuted` | - | Your microphone was unmuted |

Check warning on line 219 in calls/flutter/events.mdx

View check run for this annotation

Mintlify / Mintlify Validation (cometchat-22654f5b) - vale-spellcheck

calls/flutter/events.mdx#L219

Did you really mean 'unmuted'?
| `onVideoPaused` | - | Your camera was turned off |
| `onVideoResumed` | - | Your camera was turned on |
| `onRecordingStarted` | - | Call recording started |
Expand Down
Loading
Loading