CallKeep for Flutter

repository·master·Indexed 41 days ago

https://github.com/flutter-webrtc/callkeep

A Flutter interface for iOS CallKit and Android ConnectionService that allows apps to handle incoming calls via native system UIs, even when the app is in the background or terminated. It provides APIs for managing the call lifecycle, including displaying incoming calls, starting outgoing calls, and handling system events like answering, rejecting, or muting calls.

Tokens
5.2K
Snippets
15
Records
20
Agent score
98%

What's inside CallKeep

  1. How CallKeep manages the call lifecycle

    master

    CallKeep bridges the gap between your app's internal call logic (RTC/VoIP) and the native OS calling interfaces (CallKit on iOS, ConnectionService on Android).

    The Workflow:

    1. Trigger: An external signal (like an FCM push) arrives.
    2. Setup: callKeep.setup() is called to initialize native services.
    3. Display: callKeep.displayIncomingCall() is invoked to show the native incoming call UI.
    4. User Action: The user interacts with the native UI (e.g., taps 'Answer').
    5. Event: CallKeep emits an event (e.g., CallKeepPerformAnswerCallAction) to your Flutter code.
    6. App Logic: Your app receives the event and initiates the actual media connection (WebRTC, etc.).
    7. Control: When your app needs to end the call, it calls callKeep.endCall(uuid) to sync the native UI state.
  2. Test iOS APNS push notifications

    master

    To test incoming calls on iOS using APNS, you must first prepare a VoIP Services Certificate:

    1. Create a VoIP Services Certificate via the Apple Developer portal.
    2. Download the certificate as voip_services.cer.
    3. Install it into your macOS Keychain.
    4. Export the private key from the Keychain and rename the resulting file to callkeep-apns.p12.

    Once the certificate is ready, use the following command to trigger a push notification. Replace $ios_device_token with the actual device token of your iOS device.

    go run cmd/main.go -i +8618612345678 -p apns -d $ios_device_token
  3. Initialize CallKeep with setup()

    master

    To use CallKeep, you must call setup() when your application wakes up. This method configures platform-specific options for iOS (CallKit) and Android (ConnectionService).

    On Android, you can provide a showAlertDialog callback to handle permission requests for phone accounts via a custom UI. It is recommended to manage permissions yourself and only call setup() once permissions are granted.

    Key configuration areas include:

    • iOS: appName
    • Android: additionalPermissions and foregroundService (required for background audio on Android 11+).
    callKeep.setup(
        showAlertDialog: () async {
            // Return true to grant permission, false to deny
            return await showDialog<bool>(...);
        },
        options:<String, dynamic>{
            'ios': {
                'appName': 'CallKeepDemo',
            },
            'android': {
                'additionalPermissions': [
                    'android.permission.CALL_PHONE',
                    'android.permission.READ_PHONE_NUMBERS'
                ],
                'foregroundService': {
                    'channelId': 'com.company.my',
                    'channelName': 'Foreground service for my app',
                    'notificationTitle': 'My app is running on background',
                    'notificationIcon': 'mipmap/ic_notification_launcher',
                },
            },
        },
    );
  4. Test Android FCM push notifications

    master

    To test incoming calls on Android using FCM, you must provide a Firebase service account key:

    1. Go to your Firebase Project settings under Service Accounts.
    2. Select the Go SDK format to generate and download a service account key.
    3. Rename the downloaded file to callkeep-fcm.json.

    Once the file is in place, use the following command to trigger a push notification. Replace $android_fcm_token with the actual FCM token of your Android device.

    go run cmd/main.go -i +8618612345678 -p fcm -d $android_fcm_token
  5. Prepare the CallKeep test tool for execution

    master

    The CallKeep test tool is a Go-based utility used to demonstrate and verify the basic push process for callkeep. Before running the tool, you must initialize the Go module dependencies.

    Navigate to the tools directory and run go mod tidy to prepare the environment.

    cd tools && go mod tidy
  6. Customize the iOS launch screen assets

    master

    To change the image displayed during the app's launch on iOS, you can either replace the files directly in the file system or use Xcode.

    Option 1: File System Replace the existing image files located in example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ with your own assets.

    Option 2: Xcode (Recommended for visual management)

    1. Open the iOS workspace using: open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Locate the LaunchImage asset set and drag/drop your new images into it.
    open ios/Runner.xcworkspace
  7. Display and close incoming calls

    master

    To show an incoming call (e.g., triggered by an FCM message), you must first ensure the device has a valid phone account using hasPhoneAccount() or hasDefaultPhoneAccount().

    Once verified, use displayIncomingCall to show the native UI. To clear calls, use endAllCalls().

    Note: Use backToForeground() to bring the app to the foreground when a call is handled, allowing your app's logic to continue running even if the device was locked or the app was terminated.

    // Displaying a call
    await callKeep.displayIncomingCall(
      uuid, 
      callerIdFrom, 
      localizedCallerName: callerName, 
      hasVideo: hasVideo
    );
    callKeep.backToForeground();
    
    // Closing calls
    await callKeep.endAllCalls();
  8. Handle CallKeep event actions

    master

    CallKeep uses a set of EventType subclasses to communicate system events and user actions (like answering, rejecting, or ending calls) to your application. When interacting with the call system, you will receive these actions as events.

    Most actions are instantiated via a .fromMap(Map<dynamic, dynamic> arguments) constructor, which parses data passed from the native platform.

    Key categories of actions include:

    • Call Lifecycle: CallKeepDidReceiveStartCallAction, CallKeepPerformAnswerCallAction, CallKeepPerformEndCallAction, CallKeepPerformRejectCallAction.
    • Call State/UI: CallKeepShowIncomingCallAction, CallKeepDidDisplayIncomingCall, CallKeepDidPerformSetMutedCallAction, CallKeepDidToggleHoldAction.
    • Audio/Session: CallKeepDidChangeAudioAction, CallKeepDidActivateAudioSession, CallKeepDidDeactivateAudioSession.
    • System/Push: CallKeepPushKitToken, CallKeepDidLoadWithEvents, CallKeepProviderReset.
  9. How the EventManager typed event bus works

    master

    The EventManager provides a mechanism for decoupled communication using a typed event bus pattern.

    1. Event Definition: All events must be classes that extend EventType.
    2. Subscription: Listeners subscribe to a specific type using on<T>(callback). The EventManager ensures type safety by mapping the runtime type of the event to a list of specific callback functions.
    3. Distribution: When emit(event) is called, the manager identifies the runtime type of the provided instance and executes all registered callbacks for that type.
    4. Lifecycle: Listeners can be removed using remove<T>(callback). If a callback is not found, a debug message is logged.

    This pattern allows different parts of the callkeep package to react to lifecycle changes (like incoming calls) without being directly coupled to the source of those changes.

  10. Initialize FlutterCallkeep with setup()

    master

    To use flutter_callkeep, you must first initialize it using the setup method. This method configures platform-specific settings for both iOS and Android.

    Key Requirements:

    • iOS: The options map must contain an appName key of type String.
    • Android: Configuration is passed via the android key within the options map. You can also enable backgroundMode.
    • Alert Dialog: You can provide an optional showAlertDialog callback, which is used to ask the user for permission to open phone accounts if they are already registered.

    Platform Differences:

    • On Android, setup may trigger permission requests if additionalPermissions are provided in the Android options.
    final callkeep = FlutterCallkeep();
    
    await callkeep.setup(
      showAlertDialog: () async {
        // Implement your own UI dialog to ask user permission
        return true;
      },
      options: {
        'ios': {
          'appName': 'MyCallingApp',
        },
        'android': {
          'additionalPermissions': ['READ_PHONE_STATE'],
        },
      },
      backgroundMode: true,
    );
  11. Handle CallKeep events

    master

    CallKeep acts as an intermediary between the native calling UI and your application's call logic (Presenter/Controller/Manager). You must listen for specific events to respond to user actions taken on the native call screen.

    Common events to listen for using callKeep.on<T>(callback) include:

    • CallKeepDidDisplayIncomingCall: Triggered when an incoming call is shown.
    • CallKeepPerformAnswerCallAction: Triggered when the user answers the call.
    • CallKeepPerformEndCallAction: Triggered when the user ends the call.
    • CallKeepDidPerformSetMutedCallAction: Triggered when the user toggles mute.
    • CallKeepDidToggleHoldAction: Triggered when the user toggles hold.
    @override
    void initState() {
      super.initState();
      callKeep.on<CallKeepDidDisplayIncomingCall>(didDisplayIncomingCall);
      callKeep.on<CallKeepPerformAnswerCallAction>(answerCall);
      callKeep.on<CallKeepPerformEndCallAction>(endCall);
      callKeep.on<CallKeepDidToggleHoldAction>(didToggleHoldCallAction);
    }
    
    Future<void> answerCall(CallKeepPerformAnswerCallAction event) async {
        print('CallKeepPerformAnswerCallAction ${event.callUUID}');
        // Notify your call logic to proceed with the connection
    }
  12. Use the CallKeep test tool CLI

    master

    The CallKeep test tool is executed via go run cmd/main.go with the following flags:

    • -i: The phone number to associate with the call (e.g., +8618612345678).
    • -p: The push provider type. Use apns for iOS or fcm for Android.
    • -d: The device token (iOS) or FCM token (Android).