Skip to content

In App Calling

In-App Call SDK Setup Guide

Dependency Setup

Add the following dependency to your project:

yaml
in_app_call:
  git:
    url: [email protected]:v3/hubtel/Mobile-Apps/Platform-Library-Flutter-In-App-Call-SDK
    ref: 1.0.0  # use the latest version tag

Permissions

Android Permissions

To support video calling, add these permissions to your AndroidManifest.xml:

xml
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

⚠️ For Android 13+, request the POST_NOTIFICATIONS permission at runtime.

iOS Permissions

To support video calling, add these permissions to your Info.plist:

xml
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your camera for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your microphone for voice and video calls.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
  <string>fetch</string>
  <string>processing</string>
  <string>remote-notification</string>
  <string>voip</string>
</array>

Initializing the Call SDK

Before starting an audio call, initialize the InAppCalling service with the local participant (caller) using InAppCall.init.

Example usage:

dart
Future<void> initializeInAppCalling() async {
  final manager = Manager();
  final userFuture = await manager.getUser();

  final userName = userFuture.result?.user?.name ?? '';
  final phoneNumber = userFuture.result?.user?.phoneNumber ?? '';

  await InAppCalling.init(
    participant: Participant(
      phoneNumber: phoneNumber,
      name: userName,
      packageId: PackageId.sales,
    ), // create participant
  );
}

Handling Incoming Call Notifications

iOS Setup

In your AppDelegate.swift, import the necessary modules:

swift
import UIKit
import Flutter
import stream_video_push_notification

Then, register the app for push notifications within your AppDelegate class:

swift
override func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  GeneratedPluginRegistrant.register(with: self)

  // Register for push notifications
  StreamVideoPKDelegateManager.shared.registerForPushNotifications()

  return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

Handling Different App States

There are three possible states your app can be in when receiving an incoming call:

  1. Foreground
  2. Background
  3. Terminated

Foreground Handling:

When your app is in the foreground, handle notifications using InAppCalling.handleVoipPushNotification(message.data).

Ensure to pass it to Firebase.onMessage:

dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  FirebaseMessaging.onMessage.listen(_foregroundMessageHandler);
  //...
}

//...

Future<void> _foregroundMessageHandler(RemoteMessage message) async {
  await InAppCalling.handlePushNotification(message.data);
}

Background Handling:

For background states, reinitialize the app in a separate isolate. The handling function should be top-level and marked with @pragma('vm:entry-point'):

dart
void main() async {
  //...
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

//...

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // Initialize Firebase
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  try {
    await initializeInAppCalling();
    await InAppCalling.handlePushNotification(message.data);
  } catch (e, stk) {
    debugPrint('Error handling remote message: $e');
    debugPrint(stk.toString());
  }

  InAppCalling.reset();
}

Terminated State Handling:

Handle calls in the terminated state by initializing the app and consuming the incoming call:

dart

//Usually in the FutureBuilder of your AppStartUp
Future<AppInitializer> initializeApp(BuildContext context) async {
  if (HubtelSalesApp.navigatorKey.currentContext == null) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
      await initializeInAppCalling();
      InAppCalling.consumeIncomingCall();
    });
  }
  ...
}

To open the call screen when a call starts, pass InAppCalling.navigatorKey to your MaterialApp:

dart
class MyApp extends StatelessWidget {
  MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //...
      navigatorKey: InAppCalling.navigatorKey,
    );
  }
}

Placing a Call

To place a call to another user, use the InAppCalling.call method with the required parameters. It accepts the ID/phone number of the other user and the package ID of the app the user is on.

Example:

dart
//...
await InAppCalling.call(
  id: dialogTextController.text,
  calleePackageId: PackageId.library,
);