Signed Call Flutter SDK

Learn how to integrate Signed Call Flutter plugin to your app to use the Signed Call feature.

Overview

CleverTap's Signed Call Flutter SDK allows you to make and receive in-app calls from any Flutter application. This functionality is available if the device is connected to the internet and the SDK is properly integrated. This document provides information about the following:

  • Signed Call Flutter SDK integration - (For Android), (For iOS)
  • Manage Signed Calls from your Flutter application

To know more about the Signed Call feature, refer to Signed Call.

Prerequisites

The prerequisites vary depending on the version of your Flutter application.

For Android Platform

The following are the requirements for integrating the Signed Call Flutter SDK into the Android platform:

  • SDK version 21 and above
  • Java version 8 and above
  • Application permissions for the following:
    • Microphone (Required)
    • Notification (Required for Android 13 and above)
    • Read Phone State (Optional)

For iOS Platform

The Signed Call iOS framework is built using Swift-5 in Xcode v16. The Signed Call iOS SDK can only be used on devices running iOS 12 or above.

πŸ“˜

Emulator and Simulator Support

Voice calls are supported on emulators and simulators, but the actual voice transmission does not function.

Integrate Signed Call Flutter SDK

This process can be broadly divided into the following four major steps:

  1. Install Signed Call Flutter Plugin
  2. Set up Signed Call Native SDKs
  3. Initialize and Authenticate Signed Call Flutter SDK
  4. Manage Permissions

Install Signed Call Flutter Plugin

To install Signed Flutter SDK to your project:

  1. Add the following dependency to the pubspec.yaml file of your project:
dependencies:
    clevertap_signedcall_flutter: 0.0.7
  1. Run the flutter pub get command in the terminal to install the dependency.
  2. Add the following to your Dart code:
import 'package:clevertap_signedcall_flutter/plugin/clevertap_signedcall_flutter.dart';

Set Up Signed Call Native SDKs

This process varies depending on the type of your Flutter application platform.

For Android Platform

To install and setup the Signed Call Android SDK:

  1. Include mavenCentral in your project-level build.gradle file as follows:
allprojects {
    repositories {
        mavenCentral()
    }
}
  1. Add the following code to the dependencies element of the build.gradle file of your application module:
//To integrate Signed Call Android SDK
implementation "com.clevertap.android:clevertap-signedcall-sdk:0.0.7.4"

//To enable the socket-based signaling channel
implementation('io.socket:socket.io-client:2.1.0') {
        exclude group: 'org.json', module: 'json'
} 

//To load the image assets on the call screens
implementation 'com.github.bumptech.glide:glide:4.12.0'

//To process the incoming call push for the receiver
implementation 'androidx.work:work-runtime:2.7.1'

//To build a responsive UI for the call screens
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
  1. The Signed Call Android SDK uses CleverTap Android SDK for analytics. The Signed Call Android SDK requires an active CleverTap instance as a parameter during the SDK initialization. To integrate the CleverTap Android SDK, refer to the CleverTap Android SDK Integration.

πŸ“˜

Signed Call Flutter SDK Compatibility

The Signed Call Flutter SDK v0.0.7 is compatible with the Signed Call Android SDK 0.0.7.4 or higher and CleverTap Android SDK v5.2.2 or higher. For more details, refer to the Signed Call Flutter SDK ChangeLog.

  1. The Signed Call Android SDK uses FCM dependency to get the FCM token of the device required for the successful SDK initialization. This FCM token is then used by Signed Call to enable calls via FCM Based Call Routing Channel.
    Perform the following steps to add the FCM dependency to your application:
    1. Refer to the Firebase Integration Guide to add Firebase to your project if you have not already added it.
    2. Add the following code to the application module's dependency element:
    implementation 'com.google.firebase:firebase-messaging:21.0.0'
    

πŸ“˜

FCM Version

The minimum supported version of FCM must be v21.0.0 or higher to integrate with your Android platform.

For iOS Platform

To install and set up the Signed Call iOS SDK:

  1. Add the pod spec repo as a source and add the post_install script to your Podfile as shown below:
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'

source 'https://github.com/CleverTap/podspecs.git'
source 'https://github.com/CocoaPods/Specs.git'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
#ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!
  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    # Required by SignedCall
    target.build_configurations.each do |config|
      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
      
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
      '$(inherited)',
      
      ## dart: PermissionGroup.microphone
      'PERMISSION_MICROPHONE=1',
      ]
      
    end
  end
end
  1. Update your Podfile, and run pod install --repo-update in your terminal.
    To know more, refer to Pod setup. This command will download the latest available versions of all the transitive dependencies upon which the Signed Call Flutter SDK depends.

  2. The Signed Call iOS SDK uses CleverTap SDK for analytics. The Signed Call iOS SDK requires an active CleverTap instance as a parameter during the SDK initialization. To integrate the CleverTap iOS SDK, refer to the CleverTap iOS Integration Guide.

πŸ“˜

Signed Call Flutter SDK Compatibility

The Signed Call Flutter SDK v0.0.7 is compatible with the Signed Call iOS SDK 0.0.9 or higher and CleverTap iOS SDK v6.1.0 or higher. Running the above-mentioned pod install --repo-update command ensures that only compatible versions are downloaded.

For more details, refer to the Signed Call Flutter SDK ChangeLog.

Set Up Xcode Project

To set up your Xcode project, refer to Set Up Xcode Project.

Set Up an Outgoing Tone

To set up an outgoing tone, refer to Set Up and Outgoing Tone.

Configure Quick Launch Button on the CallKit Screen

To configure the quick launch button on the CallKit screen, refer to Configure Quick Launch Button.

πŸ“˜

Icon Image File Specifications

The icon image must be a square image with a side length of 40 points. The alpha channel of the image creates a white mask image used in the native CallKit screen UI for the button, which helps switch between the native CallKit screen and the application's calling screen.

Integrate the Signed Call iOS SDK

Integrate the Signed Call iOS SDK and CleverTap iOS SDK as follows:

  1. Open your AppDelegate file and import both, CleverTap and Signed Call SDKs as shown below:
import CleverTapSDK
import SignedCallSDK
  1. Call the registerVoIP function to generate a VoIP token and set pushRegistryDelegate in your AppDelegate file.
override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        CleverTap.setDebugLevel(CleverTapLogLevel.off.rawValue)
        CleverTap.autoIntegrate()
        SignedCall.cleverTapInstance = CleverTap.sharedInstance()
        guard let rootView = self.window?.rootViewController else {
            return true
        }
        SignedCall.registerVoIP()
        SignedCall.rootViewController = rootView
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

The registerVoIP function expects the following parameters:

ParameterDescriptionTypeNotes
rootViewThe view controller on which the call screen displays.UIViewControllerRequired
appNameThe application name you want to display on the CallKit incoming screen.StringOptional

Initialize and Authenticate Signed Call Flutter SDK

Initialize the Signed Call Flutter SDK on every application launch using the following ClevertapSignedCallFlutter class instance:

void _signedCallInitHandler(
      SignedCallError? signedCallInitError) async {
  if (signedCallInitError == null) {
      debugPrint("Signed Call SDK Initialized!");
    } else {
      final errorCode = signedCallInitError.errorCode;
      final errorMessage = signedCallInitError.errorMessage;
      final errorDescription = signedCallInitError.errorDescription;
      debugPrint("SignedCall initialization failed: \n"
          "error-code - $errorCode \n"
          "error-message - $errorMessage \n"
          "error-description - $errorDescription");
    }
  }

//To initialize the Signed Call Flutter SDK
CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

initProperties

The initProperties is a Map object with the following properties:

Properties

Description

Type

Notes

accountId

  • Unique identity of the client's account.
  • Available from the CleverTap dashboard.

String

Required

apiKey

  • Unique identity of the client's account.
  • Available from the CleverTap dashboard.

String

Required

cuid

Unique identity of the user.

String

Required

overrideDefaultBranding

It overrides the call screen’s branding set from the dashboard.
To know more, refer to its usage.

Map object

  • Optional
  • The default branding is the one you set from the dashboard.

production

It enables the VoIP push based on the build type of the iOS application.
To know more, refer to its usage.

Boolean

  • Required
  • An iOS specific property

allowPersistSocketConnection

It controls the persistence of the socket connection that SDK uses to initiate or receive the calls.
To know more, refer to its usage .

Boolean

  • Required
  • An Android specific property

notificationPermissionRequired

It indicates whether notification permission is required for the SDK initialization on Android 13 and onwards. To know more, refer to its usage.

Boolean

  • Optional
  • An Android specific property
  • The default value is true.

promptPushPrimer

It enables the Push Primer to support Android 13 changes for the Runtime Notification Permission. For more information, refer to Android 13 Changes.

Map Object

  • Optional
  • An Android specific property

promptReceiverReadPhoneStatePermission

It prompts the receiver with Read Phone State permission when the receiver answers the call.
The SDK uses Read Phone State permission to enable the busy handling for PSTN calls.
For more information, refer to Read Phone State.

Boolean

  • Optional
  • An Android specific property
  • Default value is false.

missedCallActions

It configures the action buttons for the missed call notification.

For more information, refer to missedCallActions.

Map Object

  • Optional
  • An Android specific property
  • No action buttons are displayed on a missed-call notification without this parameter.
  • A maximum of three action buttons can be configured for the missed call notification, where each entry in the map object represents an action button.

swipeOffBehaviourInForegroundService

It defines the SDK behavior during an active call when the user swipes off the app.

To know more, refer to its usage.

Enum Object

  • Optional
  • An Android specific property

fcmProcessingMode
  • Defines the mode for preprocessing incoming calls received through the FCM channel.
  • Supports the following two modes: FCMProcessingMode.foreground and FCMProcessingMode.background.
For more information, refer to Configure FCM Processing Mode.

Enum Object

  • Optional
  • An Android specific property

πŸ“˜

CUID Validation Rules

The following are the validation rules for cuid:

  • Must range between 5 and 50 characters, starting from Signed Call Flutter SDK v0.0.4; otherwise, it should be less than 15 characters.
  • Must start with either alphabet or number.
  • Must be alphanumeric, should contain at least 1 alphabet.
  • The name is case-sensitive, and only '_' is allowed as a special character.
  • The cuid parameter cannot be of type number-number, that is, a number followed by a special character, which is again followed by another number. For example, org_25 is allowed, but 91_8899555 is not permitted.
  • Must be unique for every user.
  • Must be unique for every device to allow multiple logins for the user from different devices. In such cases, the user will have multiple cuid's.

The syntax for initProperties is as follows:

///Common fields of Android & iOS
 final Map<String, dynamic> initProperties = {
        "accountId": <string / required>,
        "apiKey": <string / required>,
        "cuid": <string / required>,
        "overrideDefaultBranding": <map / optional>
 };

 ///Android only fields
 if (Platform.isAndroid) {
   initProperties["allowPersistSocketConnection"] = <bool / required>;
   initProperties["promptReceiverReadPhoneStatePermission"] = <bool / optional>;
   initProperties["missedCallActions"] = <map / optional>;
   initProperties["notificationPermissionRequired"] = <bool / optional>;
   initProperties["swipeOffBehaviourInForegroundService"] = <SCSwipeOffBehaviour enum / optional>;
 }

 ///iOS only fields
 if (Platform.isIOS) {
    initProperties["production"] = <bool / required>;
 }

overrideDefaultBranding (All Platforms)

CleverTap dashboard provides a branding tool to alter the look and feel of the call screens. If you have multiple applications to integrate with the Signed Call SDK, all those applications will share the same branding that you have set from the CleverTap dashboard. By overriding the dashboard's call screen branding, you can have different branding for each application.

Use an optional overrideDefaultBranding parameter of the Map type inside the initProperties to override the dashboard branding for call screens.

const callScreenBranding = {
        "bgColor": "<hex color code>",            //The background color of the call screens
        "fontColor": "<hex color code>",          //The color of the text displayed on the call screens
        "logoUrl": "<https url>",                 //The image URL that renders on the call screens.
        "buttonTheme": "light" or "dark"          //The theme of the control buttons shown on the ongoing call screen(i.e. Mute, Speaker and Bluetooth)
        "showPoweredBySignedCall": true or false  //An optional flag to control the label visibility on VoIP call screens. Set false to hide the label otherwise true where default value is true if not passed
};

if (Platform.isAndroid) {
  callScreenBranding["cancelCountdownColor"] = "<hex-code>"; // Default color is yellow (#F5FA55) 
}

var initProperties = {
  ....
  "overrideDefaultBranding": callScreenBranding,
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

production (iOS Platform)

The Signed Call Flutter SDK uses the production field in iOS to set up the production or development environment for VoIP push support.

  • production is used for testing VoIP push after deploying the app on TestFlight or the App Store.
  • development is used for testing VoIP push during the development phase on a simulator or device.

πŸ“˜

Note

The production must be true if you build/deploy an application for production and false if you build an application for development. The default value is false.

allowPersistSocketConnection (Android Platform)

The socket connection plays a crucial role in processing the call request to initiate a call and receive calls on the socket channel. Android OS imposes several battery restrictions that lead to issues in maintaining a persistent socket connection. To overcome this issue, Signed Call Flutter SDK expects allowPersistSocketConnection flag, a required parameter of the bool type, passed inside the initProperties constructor.

The following is the Signed Call Flutter SDK behavior for the Android platform:

ValueSocket ConnectionSigned Call Flutter SDK
truePersistentFor Android platform, the Signed Call Flutter SDK uses a background service to keep the socket connection persistent in the foreground and background states of the application.
falseNon-persistentThe Signed Call Flutter SDK does not use the background service for the socket connection; hence the socket connection may be inconsistent under battery restriction scenarios.

πŸ“˜

Recommended Settings for Android 11 and Above

For Android 11 and above, if the socket connection is persistent, the application might show a system-generated notification to the user that the application is draining the device's battery. We recommend setting the value as false and keeping the socket connection non-persistent for transactional businesses.

Android 13 Changes

All applications having Android 13 and above must request Runtime Notification Permission from the user before sending the notifications.

For the Android platform, the Signed Call Flutter SDK utilizes both local and remote notifications during VoIP calls. Therefore, notification permission is required from the user to initialize the SDK.

πŸ“˜

Signed Call Flutter SDK v0.0.2 Supports Android 13

If you want to increase the target API of your application to 33 or higher, you must upgrade the following:

  • Signed Call Flutter SDK to version 0.0.2 or higher
  • CleverTap Flutter SDK to version 1.6.0 or higher

Push Primer for Push Notification Permission

The Push Primer is a local In-App notification that educates users about the notification's context before requesting permission.

To initialize the Signed Call Flutter SDK for Android 13 and above, you can enable the Push Primer using either of the following:

Enable Push Primer via CleverTap Flutter SDK

CleverTap Flutter SDK v1.6.0 and above supports Push Primer. For more information, refer to Flutter Push Primer.

Enable Push Primer via Signed Call Flutter SDK

The Signed Call Flutter SDK enables you to display Push Primer using an optional promptPushPrimer parameter of map type inside the initProperties constructor. It ensures that the initialization happens after the notification permission is granted using Push Primer.

To configure the Push Primer:

  1. Create a Push Primer configuration using the In-App campaign's Half-Interstitial or Alert template.
//Creates push primer config using Half-Interstitial template
var pushPrimerConfig = {
  'inAppType': 'half-interstitial',
  'titleText': 'Get Notified',
  'messageText': 'Please enable notifications on your device to use Push Notifications.',
  'followDeviceOrientation': false,
  'positiveBtnText': 'Allow',
  'negativeBtnText': 'Cancel',
  'fallbackToSettings': true,
  'backgroundColor': '#FFFFFF',
  'btnBorderColor': '#000000',
  'titleTextColor': '#000000',
  'messageTextColor': '#000000',
  'btnTextColor': '#000000',
  'btnBackgroundColor': '#FFFFFF',
  'btnBorderRadius': '4',
  'imageUrl': 'https://icons.iconarchive.com/icons/treetog/junior/64/camera-icon.png'
};


//Creates push primer config using Alert template
var pushPrimerConfig = {
  'inAppType': 'alert',
  'titleText': 'Get Notified',
  'messageText': 'Enable Notification permission',
  'followDeviceOrientation': true,
  'positiveBtnText': 'Allow',
  'negativeBtnText': 'Cancel',
  'fallbackToSettings': true
};
  1. Pass the Push Primer configuration inside the promptPushPrimer parameter of the initProperties constructor. It displays the Push Primer during the SDK initialization.
var initProperties = {
  ....
  "promptPushPrimer": pushPrimerConfig,
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

πŸ“˜

Note

  • The above configuration enables the Push Primer only if the device and application both target Android 13 (API level 33) or higher.
  • To obtain the result of the push notification permission request, the Signed Call Flutter SDK registers a listener. After registration, this listener continues monitoring the permission result, even if the Push Primer prompt is displayed from the CleverTap Flutter SDK.
  • If the notification permission is denied, the Signed Call Flutter SDK returns an exception within the _signedCallInitHandler.

Configure Notification Permission as Optional (Android Platform)

The Signed Call Flutter SDK requires notification permissions to display call notifications, allowing users to interact with and return to calls. While the SDK can directly launch the call screen in the foreground, call notifications are the primary interaction method in the application's background state.

By default, the SDK needs this permission during SDK initialization, starting from Android 13. If it is not granted, the SDK returns an exception within the _signedCallInitHandler handler.

To make notification permission optional during SDK initialization, set the notificationPermissionRequired parameter of the initProperties to false, as shown below:

var initProperties = {
  ....
  "notificationPermissionRequired": <true/false>
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

πŸ“˜

Expected SDK Behaviour without Notification Access

We strongly advise against making notification permissions optional. Without this permission, the SDK can only display the call screen when the app is in the foreground. Otherwise, incoming calls are automatically declined, triggering the CallEvent.declinedDueToNotificationsDisabled event in the CleverTapSignedCallFlutter.shared.callEventListener listener.

If you choose to set notification permissions as optional, implementing the CleverTapSignedCallFlutter.shared.callEventListener listener is essential.

Manage Permission

The following permissions are required for the Signed Call Flutter SDK integration:

Microphone (All Platforms)

This is a required permission. The Signed Call Flutter SDK requires microphone permission to exchange voices during the call. At the receiver's end, the Signed Call Flutter SDK asks for microphone permission and handles it accordingly when the receiver answers the call. If the receiver denies microphone permission and the permission is blocked, the Signed Call Flutter SDK declines all upcoming calls. Furthermore, we recommend you add the required handling to request the microphone permission before initiating a call.

πŸ“˜

Note

Starting from Signed Call Flutter SDK v0.0.3, microphone permission prompt limit displayed during a VoIP call is aligned with the permissible threshold set by Android platform. Previously, the Signed Call Flutter SDK blocked all incoming calls at the receiver's end if the microphone permission was denied even once.

Read Phone State (Android Platform)

This is optional permission. The Signed Call Flutter SDK uses this permission to enable busy handling for Public Switched Telephone Network (PSTN) calls. This permission determines if the receiver is available or busy on a PSTN call. We recommend you add the required handling to request the Read Phone State permission before initiating a call.

Use the promptReceiverReadPhoneStatePermission parameter of the boolean type inside the initProperties to allow the Signed Call Flutter SDK to prompt the read phone state permission at the receiver's end when the receiver answers the call.

var initProperties = {
  ....
  "promptReceiverReadPhoneStatePermission": <true/false>
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

Logout the Signed Call Flutter SDK

When the Signed Call Flutter SDK initializes, it maintains the init configuration in a local session. Use the logout() method to invalidate the active session and disable the Signed Call functionality (make and receive a call). To enable it again, repeat the steps listed under Initialize Signed Call Flutter SDK.

CleverTapSignedCallFlutter.shared.logout();

Make a Signed Call

Use the following code to make a Signed Call:

void _signedCallVoIPCallHandler(SignedCallError? signedCallVoIPError) {
    if (signedCallVoIPError == null) {
      debugPrint("VoIP call is placed successfully!");
    } else {
      final errorCode = signedCallVoIPError.errorCode;
      final errorMessage = signedCallVoIPError.errorMessage;
      final errorDescription = signedCallVoIPError.errorDescription;
      debugPrint("VoIP call is failed: \n"
          "error-code - $errorCode \n"
          "error-message - $errorMessage \n"
          "error-description - $errorDescription");
    }
  }
  
const callOptions = {
  			"remoteContext": "<context_to_be_displayed_to_receiver / optional>",
      	"initiatorImage": "<https url / optional>",
        "receiverImage": "<https url / optional>"
      };
      
CleverTapSignedCallFlutter.shared.call(
          receiverCuid: receiverCuid,
          callContext: callContext,
          callOptions: callOptions,
          voIPCallHandler: _signedCallVoIPCallHandler);

The parameters to make a Signed Call are as follows:

Parameter

Description

Type

Required/Optional
receiverCuidIt is the receiver's cuid. StringRequired
contextCallIt specifies the context of the call. For example, the Delivery Partner is calling, the Driver is calling, the Agent is calling, and so on.
Validation rule: It must include alphanumeric characters, and the length must not exceed 64 characters.
StringRequired
callOptionsIt is a Map object with the following properties:
  • remoteContext (string): Specifies the call context to be displayed to the receiver if passed.
  • receiverImage (string): URL that displays the receiver's image to the call initiator.
  • initiatorImage (string): URL that displays the initiator's image to the receiver of the call.
Map ObjectOptional

Control Call Screen Display Timing (Android Platform)

By default, the Android SDK displays the outgoing call screen when you initiate a call request. At the same time, it starts sending a call initiation signal to the server to set up the connection.

However, you can adjust this timing to show the call screen only after the server successfully receives and confirms the call request.

To do so, use the callScreenOnSignalling parameter of type Boolean within the initProperties object. This allows the Signed Call Flutter SDK to control when the outgoing call screen appears relative to the signaling process. If you set the boolean flag to true in this method, the SDK will delay the call screen until the server completes the signaling step.

var initProperties = {
  ....
  "callScreenOnSignalling": <true/false> // default is false
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

Receive a Signed Call

Signed Call uses the following routing channels to receive Signed Calls at the receiver's end:

Socket Channel (All Platforms)

It is a primary routing channel. Signed Call Flutter SDK requires a successful initialization to receive a call on the socket channel.

FCM Channel (Android Platform)

It is a secondary or fallback routing channel used when the receiver is not connected to the primary routing channel (Socket).

To enable the FCM channel:

  1. Add your FCM Server Key to the Signed Call section of the CleverTap dashboard. If you have already added it, ignore it.

  2. Allow CleverTap Android SDK to process the FCM push of VoIP call by adding the following entries to your AndroidManifest.xml.

<application>
         ....
         ....
        <service android:name="com.clevertap.android.sdk.pushnotification.fcm.FcmMessageListenerService"
                 android:exported="true">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        ....  
 </application>
  1. Add the following code to your Application class:
CleverTapAPI.setSignedCallNotificationHandler(new SignedCallNotificationHandler());
CleverTapAPI.setSignedCallNotificationHandler(SignedCallNotificationHandler())
  1. (Optional) Configure FCM Processing Mode to define the mode for preprocessing incoming calls received through the FCM channel.

The setup is now complete to receive calls via the FCM channel.

APNs Channel (iOS Platform)

It is a secondary or fallback routing channel for the iOS platform, used when the receiver is not connected to the primary routing channel (Socket).

Set up the APNs in the Signed Call section of the CleverTap dashboard to enable the APNs channel to start receiving VoIP notifications.

Configure FCM Processing Mode

The Signed Call Flutter SDK connects to a network when receiving a call through the Firebase Cloud Messaging (FCM) channel. Starting from SDK version 0.0.7, the SDK supports the following two modes for processing FCM calls: FCMProcessingMode.foreground and FCMProcessingMode.background. These modes determine how the SDK handles incoming calls depending on whether the app is actively running or is running in the background.

The following is the Signed Call Flutter SDK behavior based on mode value:

ValueBehavior Description
FCMProcessingMode.foregroundThe SDK uses a foreground service to process FCM calls, ensuring higher priority and visibility for ongoing tasks.
NOTE: The foreground job executes quickly, and the system briefly displays and then dismisses the notification without drawing the user's attention.
FCMProcessingMode.background
  • This is the default mode.
  • The SDK processes FCM calls using a background service on a separate background thread.

To change the default FCMProcessingMode.background to FCMProcessingMode.foreground, use the following code when initializing the Signed Call Flutter SDK:

final Map<String, dynamic> fcmProcessingNotification = {
  "title": "<title>",				 //required
  "subtitle": "<subtitle>",	 //required
  "largeIcon": "<resource_name_in_android_drawable_folder>", // optional
  "cancelCtaLabel": "cancelCta", // optional
};

var initProperties = {
  ....
  "fcmProcessingMode": FCMProcessingMode.foreground,
  "fcmProcessingNotification": fcmProcessingNotification, //Required when using FCMProcessingMode.foreground
  ....
};

CleverTapSignedCallFlutter.shared.init(
  initProperties: initProperties, 
  initHandler: _signedCallInitHandler
);

The following table provides information about the parameters used in the fcmNotification object:

Method NameDescriptionTypeRequired/Optional
titleDenotes the title text. The maximum character limit is 65 characters.StringRequired
subtitleDenotes the subtitle text. The maximum character limit is 240 characters.StringRequired
largeIconDenotes the name of the drawable resource under android/app/src/res/drawable folder.StringOptional
cancelCtaLabelServes as an alternative for manual dismissal of the notification, ensuring users have an option if it does not disappear automatically.StringOptional

Close Socket Connection (All Platforms)

The Signed Call Flutter SDK uses the socket connection to signal VoIP calls from the initiator to the receiver. After successful SDK initialization, the socket connection to initiate or receive VoIP calls opens.

If the socket is left open for a longer period, the application might drain the device's battery. In the case of Android, the application might also show a system-generated notification to the user that the application is draining the device's battery. Therefore, we recommend disconnecting the socket after all expected/pending transactions are over.

To close the socket connection, use the following method as per your business use case:

CleverTapSignedCallFlutter.shared.disconnectSignallingSocket();

The following is the Signed Call Flutter SDK behavior after the SDK calls the disconnectSignallingSocket() method:

FunctionalitySigned Call Flutter SDK Behavior
Initiate a callUsers cannot initiate calls. If they attempt a VoIP call request, Signed Call Flutter SDK returns an exception within the _signedCallVoIPCallHandler method. It is essential to reinitialize the Signed Call SDK if a use case arises where a call needs to be initiated.
For example, if a user happens to place an order in the application and you want to give the option to initiate a VoIP call, the Signed Call Flutter SDK needs to be reinitialized.
Receive a callUsers can still receive calls as Signed Call uses FCM (for Android) and APNs (for iOS) as a fallback channel to receive the calls.

Call Quality Control (Android Platform)

As a preventive approach, the Signed Call Flutter SDK assesses network conditions before proceeding with a call. The behavior of the SDK differs between the initiator and receiver sides.

Initiator Side

The default behavior of the Signed Call Flutter SDK is to launch the outgoing call screen first and then check the network latency in the background. If the network latency exceeds the benchmark set by the SDK, the call request is not processed, an exception with 5004 code within the _signedCallVoIPCallHandler is returned, and the outgoing call screen is dismissed.

πŸ“˜

Note

Even when the call screen launches first, the server receives the call request only if the network conditions are acceptable.

Receiver Side

When a call is received, the SDK evaluates the network quality before showing the incoming call screen. The network quality score is calculated and passed to the app via a callback to determine if the call should continue.

The SDK provides an Android platform-specific onNetworkQualityResponse(int score) callback to handle network quality responses. The network quality score ranges from -1 to 100, allowing the app to decide whether to proceed with the call or decline it.

  • If the app returns true, the SDK continues processing the call, ensuring optimal call quality.
  • If the app returns false, the SDK declines the call and reports the CallEvent.appInitiatedCallDeclinedDueToNetworkQuality via CallEventResult instance within the CleverTapSignedCallFlutter.shared.callEventListener or CleverTapSignedCallFlutter.shared.onBackgroundCallEvent based on the app state.

The Signed Call SDK calculates the network quality score based on latency and packet loss. The score breakdown is as follows:

  • Score >= 90: Ideal voice quality.
  • Score 80-89: Good quality with the possibility of minor intermittent issues.
  • Score 70-79: The call is usable but may have some audio concerns.
  • Score 60-69: Significant audio issues, including voice breakage and delays. But, it is still operational but not ideal.
  • Score 1-59: Major issues that could lead to call drops.
  • Score 0: Possible network loss or major network connectivity concerns
  • Score -1: Indeterminate state, possibly caused by network switching, proxy issues, or misconfigured routers.

πŸ“˜

Recommendation

CleverTap recommends going ahead with the call if the Score >=70.

To implement the onNetworkQualityResponse callback in your Android app,
integrate the SignedCallAPI.getInstance().setNetworkQualityCheckHandler(handler) in the Application class to ensure the callback is triggered even when the app is not running at all.

SignedCallAPI.getInstance().setNetworkQualityCheckHandler(new SCNetworkQualityHandler() {
    @Override
    public boolean onNetworkQualityResponse(final int score) {
        Log.d(LOG_TAG, "Network quality score: " + score);

        // Return true to proceed with the call, or false to reject the call
        return true/false;
    }
});
SignedCallAPI.getInstance().setNetworkQualityCheckHandler(object : SCNetworkQualityHandler {
    override fun onNetworkQualityResponse(score: Int): Boolean {
        Log.d(LOG_TAG, "Network quality score: $score")

        // Return true to proceed with the call, or false to reject the call
        return true // or false
    }
})

πŸ“˜

Important

  • The SDK waits 500 ms for a response from onNetworkQualityResponse. If no response is received, it proceeds with the call. Therefore, ensure that you return the boolean response within this window.
  • The SDK invokes the onNetworkQualityResponse callback on a background thread. As a result, avoid performing any UI-related operations directly within this callback. If required, you can manually switch to the main thread.

Configure Swipe Off Behaviour (Android Platform)

By default, the Signed Call Flutter SDK terminates a call when a user swipes off the call screen from the recent task screen. However, you can modify this default behavior if your app runs a foreground service to maintain the operation even after the app's swipe-off. The SDK allows you to override the default behavior and persist the call within the foreground service managed by your application.

To change the swipe-off behavior, use the swipeOffBehaviourInForegroundService parameter in initProperties. Pass the enum constant SCSwipeOffBehaviour.persistCall to keep the call persistent in a foreground service after swiping off. The default value is SCSwipeOffBehaviour.endCall.

To change the default swipe-off behavior, use the following snippet:

var initProperties = {
  ....
  "swipeOffBehaviourInForegroundService": <SCSwipeOffBehaviour.persistCall or SCSwipeOffBehaviour.endCall>
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);

Handle Call Hangup (All Platforms)

The call hangup functionality is user-driven, and the user's decision to end the call depends on it. For example, if one of the users in a call clicks the call hangup button from the ongoing call screen, the Signed Call Flutter SDK internally manages the call hangup functionality to end the call.

In the case of a metered call, when a business wants to end the call after a specific duration, you must maintain a timer in the application and use the following method to terminate the call when the timer ends:

CleverTapSignedCallFlutter.shared.hangUpCall();

Handle Call Events (All Platforms)

Add the following code to subscribe to the event stream of the CallEventResult instance to receive the changes in a call state:

late StreamSubscription<CallEvent>? _callEventSubscription;

@override
void initState() {
    super.initState();
    _startObservingCallEvents();
}
  
///To Listen to the real-time stream of the various call-events
void _startObservingCallEvents() {
    _callEventSubscription =
        CleverTapSignedCallFlutter.shared.callEventListener.listen((result) {
      debugPrint(
          "CleverTap:SignedCallFlutter: received callEvent stream with ${result.toString()}");
      if (result.callEvent == CallEvent.callIsPlaced) {
        // Indicates that the call is successfully placed
      } else if (result.callEvent == CallEvent.callIsPlaced) {
        // Indicates that the call starts ringing on the receiver's device
      } else if (result.callEvent == CallEvent.cancelled) {
        // Indicates that the call is cancelled from the initiator's end
      } else if (result.callEvent === CallEvent.cancelledDueToRingTimeout) {
        // [Specific to Android-Platform]
        // Indicates that the call is cancelled due to a ring timeout(35 secs)
      } else if (result.callEvent == CallEvent.declined) {
        // Indicates that the call is declined from the receiver's end
      } else if (result.callEvent == CallEvent.missed) {
        // Indicates that the call is missed at the receiver's end
      } else if (result.callEvent == CallEvent.answered) {
        // Indicates that the call is picked up by the receiver
      } else if (result.callEvent == CallEvent.callInProgress) {
        // Indicates that the connection to the receiver is established and the audio transfer begins at this stateaa
      } else if (result.callEvent == CallEvent.ended) {
        // Indicates that the call has been ended
      } else if (result.callEvent === CallEvent.receiverBusyOnAnotherCall) {
        // Indicates that the receiver is already busy on another call
      } else if (result.callEvent === CallEvent.declinedDueToBusyOnVoIP) {
        // [Specific to Android-Platform]
        // Indicates that the receiver is busy on VoIP call
      } else if (result.callEvent === CallEvent.declinedDueToBusyOnPSTN) {
        // [Specific to Android-Platform]
        // Indicates that the receiver is busy on PSTN call
      } else if (result.callEvent == CallEvent.declinedDueToLoggedOutCuid) {
        // Indicates that the call is declined due to the receiver being logged out with the specific CUID
      } else if (result.callEvent == CallEvent.declinedDueToNotificationsDisabled) {
        // [Specific to Android-Platform]
  			// Indicates that the call is declined due to the notifications are disabled at the receiver's end
      } else if (result.callEvent == CallEvent.declinedDueToMicrophonePermissionsNotGranted) {
        // Indicates that the microphone permission is not granted for the call
      } else if (result.callEvent == CallEvent.declinedDueToMicrophonePermissionBlocked) {
        // [Specific to Android-Platform]
        // Indicates that the microphone permission is blocked at the receiver's end.
      } else if (result.callEvent == CallEvent.failedDueToInternalError) {
        // [Specific to Android-Platform]
        // Indicates that the call is failed after signalling. Possible reasons could include low internet connectivity, low RAM available on device, SDK fails to set up the voice channel within the time limit
      } else if (result.callEvent == CallEvent.appInitiatedCallDeclinedDueToNetworkQuality) {
        // [Specific to Android-Platform]
        // Indicates that the call is declined from `onNetworkQualityResponse(int score)` callback based on the score provided
      }
    });
}

@override
void dispose() {
    super.dispose();
    _callEventSubscription?.cancel();
}

Ensure that you register the StreamSubscription<CallEvent> instance as soon as the Flutter application is initialized, preferably in the initState() of your root widget and cancel the StreamSubscription instance when the dispose() method is triggered for the root widget.

πŸ“˜

Breaking Change in Signed Call Flutter SDK v0.0.4

The CleverTapSignedCallFlutter.shared.callEventListener event stream will now provide an instance of the CallEventResult class instead of the CallEvent class.

πŸ“˜

[Android Platform] The callEventListener doesn't receive updates in killed state

Please note that, in case of Android platform, the callEventListener stream receive updates only when the app is in the foreground or background states, and not when it has been terminated(killed).
Refer onBackgroundCallEvent(handler) callback handling section to handle the call-events in the killed state.

Retrieve Current Call State (Android Platform)

To retrieve the current call state, the Signed Call Flutter SDK exposes the getCallState() method via CleverTapSignedCallFlutter class. To retrieve the current call state, use the following method:

SCCallState? callState = await CleverTapSignedCallFlutter.shared.getCallState();
debugPrint("CallState is: => callState")

Possible call states returned by the SDK include OutgoingCall, IncomingCall, OngoingCall, CLEANUP_CALL, and NoCall. The NoCall state is returned when there is no ongoing active call.

Return to Active Call (Android Platform)

To return to an active call, the Signed Call Flutter SDK exposes the getBackToCall() method via CleverTapSignedCallFlutter class. To return to an active call, use the following method:

CleverTapSignedCallFlutter.shared.getBackToCall().then((bool result) {
    if (!result) {
      debugPrint("No active call, invalid operation!");
    }
 });

Missed Call Solution (Android Platform)

If the receiver misses a call, the Signed Call Flutter SDK shows a missed call notification to the receiver. The Signed Call Flutter SDK uses action buttons on the missed call notification to display a Call to Action (CTA).

To configure the CTA on the missed call notification, add the following code:

  1. Create a Map object for missedCallActions parameter with a maximum of three entries.
const missedCallActionsMap = {
      "<Unique Identifier 1>": "<label on action-button 1>",
      "<Unique Identifier 2>": "<label on action-button 2>",
      "<Unique Identifier 3>": "<label on action-button 3>",
 };

var initProperties = {
  ....
  "missedCallActions": missedCallActionsMap,
  ....
};

CleverTapSignedCallFlutter.shared.init(
      initProperties: initProperties, initHandler: _signedCallInitHandler);
  1. Add the following code to subscribe to the event stream of the MissedCallActionClickResult to listen the CTA click events on missed call notifications:
late StreamSubscription<MissedCallActionClickResult>?
    _missedCallActionClickEventSubscription;

@override
void initState() {
    super.initState();
    _startObservingMissedCallActionClickEvent();
}
  
///Listens to the missed call action click events
void _startObservingMissedCallActionClickEvent() {
     _missedCallActionClickEventSubscription = CleverTapSignedCallFlutter.shared.missedCallActionClickListener
        .listen((clickResult) {
      debugPrint("Received an event on MissedCallActionClickResult stream channel: "
          "${clickResult.toString()}");
    });
}

@override
void dispose() {
    super.dispose();
    _missedCallActionClickEventSubscription?.cancel();
}

Ensure that you register the StreamSubscription<MissedCallActionClickResult> instance as soon as the Flutter application is initialized, preferably in the initState() of your root widget and cancel the StreamSubscription instance when the dispose() method is triggered for the root widget.

πŸ“˜

Manage missedCallActionClickListener for Android in Killed state

  • For Android platform, the missedCallActionClickListener stream only receives updates when the app is in the foreground or background states, not when terminated.
  • Refer to theonBackgroundMissedCallActionClicked(handler) in callback handling section to handle the call-events in the killed state.

Handle Callbacks in Killed state (Android Platform)

The Signed Call Flutter SDK v0.0.5 and above provides Android-specific callback APIs to handle the following events when the app is terminated or killed:

Handle Call Events in the Killed State

The Signed Call Flutter SDK provides the onBackgroundCallEvent method to register a handler for managing call events in the killed state.

To set up the onBackgroundCallEvent handler:

  1. Add the following code to the onCreate() method of your Application class:
    SCBackgroundCallEventHandler.initialize(this);
    
    SCBackgroundCallEventHandler.initialize(this)
    
  2. Register the onBackgroundCallEvent handler to receive updates on call events along with the CallEventResult instance. When a VoIP call is received in the killed state, SDK spawns an isolate (Android only), allowing you to handle call events even when your application is not running.
import 'package:clevertap_signedcall_flutter/models/call_event_result.dart';

@pragma('vm:entry-point')
void _backgroundCallEventHandler(CallEventResult result) async {
  debugPrint("backgroundCallEventHandler called from headless task with payload: $result");
}

void main() {
  CleverTapSignedCallFlutter.shared.onBackgroundCallEvent(_backgroundCallEventHandler);
  runApp(MyApp());
}

Managing Click Events for Missed Call Notifications in the Killed State

The Signed Call Flutter SDK provides the onBackgroundMissedCallActionClicked method to register a handler for managing missed call CTA click events in the killed state.

To set up the onBackgroundMissedCallActionClicked handler:

  1. Add the following code to the onCreate() method of your Application class:
SCBackgroundCallEventHandler.initialize(this);

SCBackgroundCallEventHandler.initialize(this)

πŸ“˜

Note

You can skip the above step if you have already completed this step when handling call events in the killed state.

  1. Register the onBackgroundMissedCallActionClicked handler to receive updates on CTA click along with the MissedCallActionClickResult instance. When a missed call CTA is clicked in the killed state, SDK spawns an isolate (Android only), allowing you to handle missed call CTA click events even when your application is not running.
import 'package:clevertap_signedcall_flutter/models/missed_call_action_click_result.dart';

@pragma('vm:entry-point')
void _backgroundMissedCallActionClickedHandler(MissedCallActionClickResult result) async {
  debugPrint("backgroundMissedCallActionClickedHandler called from headless task with payload: $result");
}

void main() {
  CleverTapSignedCallFlutter.shared.onBackgroundMissedCallActionClicked(_backgroundMissedCallActionClickedHandler);
  runApp(MyApp());
}

πŸ“˜

Important

There are a few things to keep in mind about your onBackgroundCallEvent and onBackgroundMissedCallActionClicked handlers:

  • They must not be an anonymous function.
  • They must be a top-level function. For example, not a class method that requires initialization.
  • When using Flutter version 3.3.0 or higher, the handlers must be annotated with @pragma('vm:entry-point') right above the function declaration (otherwise, it may be removed during tree shaking for release mode).
  • Add the handlers to your main.dart file, right after the import statements, and outside any Widget class declaration, so that they can be invoked via a Flutter background isolate.

Debugging (All Platforms)

Signed Call Flutter SDK logs are, by default, set to the LogLevel.info level. We recommend you set the log level to LogLevel.verbose mode to log warnings or other important messages during development. If you want to disable the Signed Call Flutter SDK logs for the production environment, you can set the debug level to LogLevel.off.

CleverTapSignedCallFlutter.shared.setDebugLevel(LogLevel.info); ///default level, shows minimal SDK integration related logging

CleverTapSignedCallFlutter.shared.setDebugLevel(LogLevel.debug); ///shows debug output

CleverTapSignedCallFlutter.shared.setDebugLevel(LogLevel.verbose); ///shows verbose output

CleverTapSignedCallFlutter.shared.setDebugLevel(LogLevel.off);  ///disables all debugging

The log window displays the logs from the Signed Call SDKs. After setting the debug level, search for the following tags:

PlatformTAG
Android[CT]:[SignedCall]
iOS[Signed Call]
Fluter[CT]:[SignedCall]:[Flutter]

πŸ“˜

Note

To get the logs in the killed state, add platform-specific debugging.

  • For Android, add SignedCallAPI.setDebugLevel(SignedCallAPI.LogLevel loglevel); in your Application class.
  • For iOS, add SignedCall.isLoggingEnabled = true in your AppDelegate class.

Sample Project of Signed Call Flutter SDK Implementation:

For an example project of integrating Signed Call Flutter SDK in your Flutter application, refer to the Signed Call Flutter Example Project.

πŸ“˜

Note

Ensure you have added valid Signed Call and CleverTap Account credentials for the calling to work.

Error Handling (All Platforms)

The Signed Call Flutter SDK provides error reporting and handling. The initHandler passed inside the init(..) method reports all the initialization errors, whereas the voIPCallHandler of the call(..) method reports all the Call errors.

For Android Platform

Below is the list of the initialization and call errors that you may receive for Android platform:

Error CodeDescription
1000No internet connection.
2000The application context is missing.
2001The CleverTapApi instance is missing.
2002The initProperties is missing.
2003The Signed Call Android SDK is not initialized.
2004The accountId and apiKey parameters are missing.
2005The cuid is missing.
2006The cuid length is invalid.
2007Invalid cuid due to violation of valid cuid rules.
2008The length of the name parameter is invalid.
2009The appId is invalid.
2010The branding configuration is invalid.
2011The values in initProperties are invalid.
2012The user authentication is not successful.
2013The notification permission was not given during the SDK initialization.
5001Microphone permission is not available.
5002The Internet connection is lost at the receiver's end before the call connects.
5003The receiver is unreachable.
5004The Signed Call Android SDK can not initiate the call because of a poor network.
5005The Receiver and Initiator's cuid is the same.
5006The context of the call is missing.
5007The length of the context message exceeds the limit of 64 characters.
5008Invalid context of the application.
5009The receiver's cuid is missing.
5010The signed call can not be initiated to the unregistered/invalid cuid.
5011The socket required to initiate a call is not connected to the signaling channel.
5012The callOptions parameters are invalid.
5013Unable to process new call requests as the Signed Call Android SDK is already processing some other request.
5014The call feature is not enabled to initiate the call.

For iOS Platform

Below is the list of the initialization and call errors that you may receive for iOS platform:

Error CodeDescription
1000No internet connection.
2000The context of the call is invalid.
2001The CleverTap instance is not available.
2002The CleverTap iOS SDK's base URL was not found.
2003Signed Call iOS SDK is not initialized.
2004The Signed Call accountId is required.
2005The apiKey is required.
2006The cuid is missing.
2007The cuid length is invalid.
2008Invalid cuid due to violation of valid cuid rules.
2009The length of the name parameter is invalid.
2010The appId is invalid.
2011The production key is missing.
2012Contact registration failed.
2013The VoIP service failed to register.
5001Microphone permission is not granted.
5002The receiver is not reachable.
5003The initiator and receiver's cuid is the same.
5004The cuid is connected to another device.
5005Failure while making a call.
5006The call feature is not enabled to initiate the call.
5012The socket required to initiate a call is not connected to the signaling channel.

FAQs

Q. Is Signed Call accountId and apiKey the same as CleverTap's accountId and token?

A. No. Signed Call accountId and apiKey differ from CleverTap's accountId and token. You can find these details under your dashboard's Signed Call Settings.

Q. Why are the iOS dependencies of Signed Call not downloaded even after running 'pod install'?

A. The iOS dependencies for Signed Call are not downloaded because the Signed Call iOS SDK uses submodules. To resolve this issue, perform pod repo update to download and install the required dependencies.

Q. Does the Signed Call Flutter SDK support in-app calls over Bluetooth?

A. Yes, the Signed Call Flutter SDK does support in-app calls over Bluetooth. However, it requires runtime BLUETOOTH_CONNECT permission for Android 12 and onwards.

Q. What channels does the Signed Call Flutter SDK use for call routing?

A. The Signed Call Flutter SDK employs an active socket connection immediately upon initialization. The socket connection serves as the primary routing channel to receive calls. In case the receiver is not connected to the socket channel, FCM is utilized on the Android platform, and APNs is used on the iOS platform as fallback channels. The socket connection processes the call requests raised to make a call. To know more, refer to the Best practices for initializing Signed Call SDKs.