flutter_callkit_incoming

3.1.6

Flutter Callkit Incoming to show callkit screen in your Flutter app.

Download archive

Readme

Flutter Callkit Incoming

A Flutter plugin to show incoming call in your Flutter app (Custom for Android/Callkit for iOS).

pub package pub points GitHub stars GitHub forks GitHub license Build Status

Sponsors

Our top sponsors are shown below!

📌 Table of Contents

⭐ Features

  • Show an incoming call
  • Start an outgoing call
  • Custom UI Android/Callkit for iOS
  • Example using Pushkit/VoIP for iOS

[!WARNING]

iOS: ONLY WORKING ON REAL DEVICE

Please make sure setup/using PUSHKIT FOR VOIP

Note: Please do not use on simulator (Callkit framework is not fully supported on simulator for incoming VoIP calls).

🚀 Installation

1. Install Packages

For version >= v2.5.0, please make sure install and use Java SDK version >= 17 (Android)

Run this command:

flutter pub add flutter_callkit_incoming

Or add to pubspec.yaml:

dependencies:
  flutter_callkit_incoming: ^latest

2. Configure Project

Android

AndroidManifest.xml:

<manifest...>
    ...
    <!-- Using for load image from internet -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <application ...>
        <activity ...
            android:name=".MainActivity"
            android:launchMode="singleInstance"><!-- add this -->
        ...
    </application>
</manifest>

Proguard Rules: The following rule needs to be added in the proguard-rules.pro to avoid obfuscated keys:

-keep class com.hiennv.flutter_callkit_incoming.** { *; }

iOS

Info.plist:

<key>UIBackgroundModes</key>
<array>
    <string>voip</string>
    <string>remote-notification</string>
    <string>processing</string> <!-- you can add this if needed -->
</array>

Swift Package Manager (SPM) Support (Flutter 3.24+)

This plugin natively supports Swift Package Manager (SPM). To configure your project to build using SPM instead of CocoaPods, run:

flutter config --enable-swift-package-manager

[!IMPORTANT] Swift Package Manager is highly recommended for modern Flutter projects as CocoaPods is in maintenance mode and will become read-only in December 2026.

3. Usage

Import

import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';

Show Incoming Call

this._currentUuid = _uuid.v4();
CallKitParams callKitParams = CallKitParams(
  id: _currentUuid,
  nameCaller: 'Hien Nguyen',
  appName: 'Callkit',
  avatar: 'https://i.pravatar.cc/100',
  handle: '0123456789',
  type: 0,
  textAccept: 'Accept',
  textDecline: 'Decline',
  missedCallNotification: NotificationParams(
    showNotification: true,
    isShowCallback: true,
    subtitle: 'Missed call',
    callbackText: 'Call back',
  ),
  callingNotification: const NotificationParams(
    showNotification: true,
    isShowCallback: true,
    subtitle: 'Calling...',
    callbackText: 'Hang Up',
  ),
  duration: 30000,
  extra: <String, dynamic>{'userId': '1a2b3c4d'},
  headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
  android: const AndroidParams(
    isCustomNotification: true,
    isShowLogo: false,
    logoUrl: 'https://i.pravatar.cc/100',
    ringtonePath: 'system_ringtone_default',
    backgroundColor: '#0955fa',
    backgroundUrl: 'https://i.pravatar.cc/500',
    actionColor: '#4CAF50',
    textColor: '#ffffff',
    incomingCallNotificationChannelName: "Incoming Call",
    missedCallNotificationChannelName: "Missed Call",
    isShowCallID: false
  ),
  ios: IOSParams(
    iconName: 'CallKitLogo',
    handleType: 'generic',
    supportsVideo: true,
    maximumCallGroups: 2,
    maximumCallsPerCallGroup: 1,
    audioSessionMode: 'default',
    audioSessionActive: true,
    audioSessionPreferredSampleRate: 44100.0,
    audioSessionPreferredIOBufferDuration: 0.005,
    supportsDTMF: true,
    supportsHolding: true,
    supportsGrouping: false,
    supportsUngrouping: false,
    ringtonePath: 'system_ringtone_default',
  ),
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);

Note: For Firebase Message: @pragma('vm:entry-point')
https://github.com/firebase/flutterfire/blob/master/docs/cloud-messaging/receive.md#apple-platforms-and-android

Request Notification Permission (Android 13+/iOS)

For Android 13+, please requestNotificationPermission or requestPermission of firebase_messaging before showCallkitIncoming:

await FlutterCallkitIncoming.requestNotificationPermission({
  "title": "Notification permission",
  "rationaleMessagePermission": "Notification permission is required, to show notification.",
  "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting."
});

Request Full Intent Permission (Android 14+)

For Android 14+, please use canUseFullScreenIntent and requestFullIntentPermission:

// Check if can use full screen intent
await FlutterCallkitIncoming.canUseFullScreenIntent();

// Request full intent permission
await FlutterCallkitIncoming.requestFullIntentPermission();

Show Missed Call Notification

this._currentUuid = _uuid.v4();
CallKitParams params = CallKitParams(
  id: _currentUuid,
  nameCaller: 'Hien Nguyen',
  handle: '0123456789',
  type: 1,
  missedCallNotification: const NotificationParams(
    showNotification: true,
    isShowCallback: true,
    subtitle: 'Missed call',
    callbackText: 'Call back',
  ),
  android: const AndroidParams(
    isCustomNotification: true,
    isShowCallID: true,
  ),
  extra: <String, dynamic>{'userId': '1a2b3c4d'},
);
await FlutterCallkitIncoming.showMissCallNotification(params);

Hide Call Notification (Android)

CallKitParams params = CallKitParams(
  id: _currentUuid,
);
await FlutterCallkitIncoming.hideCallkitIncoming(params);

Start Outgoing Call

this._currentUuid = _uuid.v4();
CallKitParams params = CallKitParams(
  id: this._currentUuid,
  nameCaller: 'Hien Nguyen',
  handle: '0123456789',
  type: 1,
  extra: <String, dynamic>{'userId': '1a2b3c4d'},
  ios: IOSParams(handleType: 'generic'),
  callingNotification: const NotificationParams(
    showNotification: true,
    isShowCallback: true,
    subtitle: 'Calling...',
    callbackText: 'Hang Up',
  ),
  android: const AndroidParams(
    isCustomNotification: true,
    isShowCallID: true,
  )
);
await FlutterCallkitIncoming.startCall(params);

End Call

// End specific call
await FlutterCallkitIncoming.endCall(this._currentUuid);

// End all calls
await FlutterCallkitIncoming.endAllCalls();

Get Active Calls

iOS: returns active calls from Callkit (only id), Android: only returns last call

await FlutterCallkitIncoming.activeCalls();

Output:

[{"id": "8BAA2B26-47AD-42C1-9197-1D75F662DF78", ...}]

Set Call Connected

Used to determine Incoming Call or Outgoing Call status in phone book(reset/start timer):

await FlutterCallkitIncoming.setCallConnected(this._currentUuid);

Note: After the call is ACCEPT or startCall, please call this function. Normally it should be called when WebRTC/P2P is established.

Get Device Push Token VoIP

iOS: returns deviceToken, Android: returns none

await FlutterCallkitIncoming.getDevicePushTokenVoIP();

Output:

d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd

Important: Make sure using SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken) inside AppDelegate.swift (Example)

func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
    print(credentials.token)
    let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
    // Save deviceToken to your server
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}

func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
    print("didInvalidatePushTokenFor")
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}

Listen Events

FlutterCallkitIncoming.onEvent.listen((CallEvent event) {
  switch (event!.event) {
    case Event.actionCallIncoming:
      // TODO: received an incoming call
      break;
    case Event.actionCallStart:
      // TODO: started an outgoing call
      // TODO: show screen calling in Flutter
      break;
    case Event.actionCallAccept:
      // TODO: accepted an incoming call
      // TODO: show screen calling in Flutter
      break;
    case Event.actionCallDecline:
      // TODO: declined an incoming call
      break;
    case Event.actionCallEnded:
      // TODO: ended an incoming/outgoing call
      break;
    case Event.actionCallTimeout:
      // TODO: missed an incoming call
      break;
    case Event.actionCallCallback:
      // TODO: click action `Call back` from missed call notification
      break;
    case Event.actionCallToggleHold:
      // TODO: only iOS
      break;
    case Event.actionCallToggleMute:
      // TODO: only iOS
      break;
    case Event.actionCallToggleDmtf:
      // TODO: only iOS
      break;
    case Event.actionCallToggleGroup:
      // TODO: only iOS
      break;
    case Event.actionCallToggleAudioSession:
      // TODO: only iOS
      break;
    case Event.actionDidUpdateDevicePushTokenVoip:
      // TODO: only iOS
      break;
    case Event.actionCallCustom:
      // TODO: for custom action
      break;
  }
});

Call from Native (iOS/Android)

Swift (iOS):

var info = [String: Any?]()
info["id"] = "44d915e1-5ff4-4bed-bf13-c423048ec97a"
info["nameCaller"] = "Hien Nguyen"
info["handle"] = "0123456789"
info["type"] = 1
// ... set more data
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(flutter_callkit_incoming.Data(args: info), fromPushKit: true)

// Please make sure call `completion()` at the end of the pushRegistry(......, completion: @escaping () -> Void)
// or `DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { completion() }`
// if you don't call completion() in pushRegistry(......, completion: @escaping () -> Void), there may be app crash by system when receiving VoIP

Kotlin (Android):

FlutterCallkitIncomingPlugin.getInstance().showIncomingNotification(...)

Alternative Swift approach:

let data = flutter_callkit_incoming.Data(id: "44d915e1-5ff4-4bed-bf13-c423048ec97a", nameCaller: "Hien Nguyen", handle: "0123456789", type: 0)
data.nameCaller = "Johnny"
data.extra = ["user": "abc@123", "platform": "ios"]
// ... set more data
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)

Objective-C:

#if __has_include(<flutter_callkit_incoming/flutter_callkit_incoming-Swift.h>)
#import <flutter_callkit_incoming/flutter_callkit_incoming-Swift.h>
#else
#import "flutter_callkit_incoming-Swift.h"
#endif

Data * data = [[Data alloc]initWithId:@"44d915e1-5ff4-4bed-bf13-c423048ec97a" nameCaller:@"Hien Nguyen" handle:@"0123456789" type:1];
[data setNameCaller:@"Johnny"];
[data setExtra:@{ @"userId" : @"HelloXXXX", @"key2" : @"value2"}];
// ... set more data
[SwiftFlutterCallkitIncomingPlugin.sharedInstance showCallkitIncoming:data fromPushKit:YES];

Send Custom Event from Native:

Swift:

SwiftFlutterCallkitIncomingPlugin.sharedInstance?.sendEventCustom(body: ["customKey": "customValue"])

Kotlin:

FlutterCallkitIncomingPlugin.getInstance().sendEventCustom(body: Map<String, Any>)

Call API when Accept/Decline/End/Timeout

Setup for Missed call notification(iOS)

AppDelegate.swift:

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        // Setup VOIP
        let mainQueue = DispatchQueue.main
        let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
        voipRegistry.delegate = self
        voipRegistry.desiredPushTypes = [PKPushType.voIP]

        // Use if using WebRTC
        // RTCAudioSession.sharedInstance().useManualAudio = true
        // RTCAudioSession.sharedInstance().isAudioEnabled = false

        //Add for Missed call notification
        if #available(iOS 10.0, *) {
          UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
        }
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    // Add for Missed call notification(show notification when foreground)
    override func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler:
                                   @escaping (UNNotificationPresentationOptions) -> Void) {
        
        CallkitNotificationManager.shared.userNotificationCenter(center, willPresent: notification, withCompletionHandler: completionHandler)
    }
    
    // Add for Missed call notification(action when click callback in missed notification)
    override func userNotificationCenter(_ center: UNUserNotificationCenter,
                                         didReceive response: UNNotificationResponse,
                                         withCompletionHandler completionHandler: @escaping () -> Void) {
        if response.actionIdentifier == CallkitNotificationManager.CALLBACK_ACTION {
            let data = response.notification.request.content.userInfo as? [String: Any]
            SwiftFlutterCallkitIncomingPlugin.sharedInstance?.sendCallbackEvent(data)
        }
        completionHandler()
    }

    // Func Call API for Accept
    func onAccept(_ call: Call, _ action: CXAnswerCallAction) {
        let json = ["action": "ACCEPT", "data": call.data.toJSON()] as [String: Any]
        print("LOG: onAccept")
        self.performRequest(parameters: json) { result in
            switch result {
            case .success(let data):
                print("Received data: \(data)")
                // Make sure call action.fulfill() when you are done (connected WebRTC - Start counting seconds)
                action.fulfill()
            case .failure(let error):
                print("Error: \(error.localizedDescription)")
            }
        }
    }
    
    // Func Call API for Decline
    func onDecline(_ call: Call, _ action: CXEndCallAction) {
        let json = ["action": "DECLINE", "data": call.data.toJSON()] as [String: Any]
        print("LOG: onDecline")
        self.performRequest(parameters: json) { result in
            switch result {
            case .success(let data):
                print("Received data: \(data)")
                // Make sure call action.fulfill() when you are done
                action.fulfill()
            case .failure(let error):
                print("Error: \(error.localizedDescription)")
            }
        }
    }
    
    // Func Call API for End
    func onEnd(_ call: Call, _ action: CXEndCallAction) {
        let json = ["action": "END", "data": call.data.toJSON()] as [String: Any]
        print("LOG: onEnd")
        self.performRequest(parameters: json) { result in
            switch result {
            case .success(let data):
                print("Received data: \(data)")
                // Make sure call action.fulfill() when you are done
                action.fulfill()
            case .failure(let error):
                print("Error: \(error.localizedDescription)")
            }
        }
    }
    
    func onTimeOut(_ call: Call) {
        let json = ["action": "TIMEOUT", "data": call.data.toJSON()] as [String: Any]
        print("LOG: onTimeOut")
        self.performRequest(parameters: json) { result in
            switch result {
            case .success(let data):
                print("Received data: \(data)")
            case .failure(let error):
                print("Error: \(error.localizedDescription)")
            }
        }
    }

    func didActivateAudioSession(_ audioSession: AVAudioSession) {
        // Use if using WebRTC
        // RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
        // RTCAudioSession.sharedInstance().isAudioEnabled = true
    }
    
    func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
        // Use if using WebRTC
        // RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
        // RTCAudioSession.sharedInstance().isAudioEnabled = false
    }
}

Please check full example: Example **Create MMainApplication.kt in your source directory **

MainApplication.kt:

import android.app.Application
import android.os.Bundle
import android.content.Context
import android.content.Intent
import android.util.Log
import com.hiennv.flutter_callkit_incoming.CallkitEventCallback
import com.hiennv.flutter_callkit_incoming.FlutterCallkitIncomingPlugin
import io.flutter.embedding.android.FlutterActivity
import org.json.JSONObject
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL

private const val BASE_URL = "https://your-url/"
private const val TAG = "com.medeet.app.MainApplication"


class MainApplication : Application() {  // or FlutterApplication


  private var callkitEventCallback = object: CallkitEventCallback{
    override fun onCallEvent(event: CallkitEventCallback.CallEvent, callData: Bundle) {
      when (event) {
        CallkitEventCallback.CallEvent.ACCEPT -> {
          // Save accepted call id to SharedPreferences
          Log.d(TAG, "onAccept - Kotlin")

        }
        CallkitEventCallback.CallEvent.DECLINE -> {
          Log.d(TAG, "on Decline - Kotlin")
          val extra = callData.getSerializable("EXTRA_CALLKIT_EXTRA") as? HashMap<String, Any?>
          Log.d(TAG, "on Decline - $extra")
          sendDeclineCall(extra)
        }
        else -> {
          // Handle other cases or do nothing
        }
      }

    }
  }

  //Replace with your custom integration
  private fun sendDeclineCall(extra: HashMap<String, Any?>?) {
    // Read access token from SharedPreferences
    val prefs = applicationContext.getSharedPreferences(
      "FlutterSharedPreferences", Context.MODE_PRIVATE
    )
    val accessToken = prefs.getString("flutter.native_access_token", null)

    if (accessToken.isNullOrEmpty()) {
      Log.d(TAG, "declineCall - No access token found")
      return
    }
    val jsonBody = JSONObject((extra ?: emptyMap<String, Any?>()) as Map<*, *>)


    Log.d(TAG, "declineCall - body: $jsonBody")

    // Send request on background thread
    Thread {
      try {
        val url = URL("$BASE_URL/declineCall")
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "POST"
        connection.setRequestProperty("Content-Type", "application/json")
        connection.setRequestProperty("Accept", "application/json")
        connection.setRequestProperty("Authorization", "Bearer $accessToken")
        connection.doOutput = true

        val writer = OutputStreamWriter(connection.outputStream)
        writer.write(jsonBody.toString())
        writer.flush()
        writer.close()
        val responseCode = connection.responseCode
        Log.d(TAG, "declineCall - Response status: $responseCode")
        connection.disconnect()
      } catch (e: Exception) {
        Log.e(TAG, "declineCall - Request error: ${e.message}")
      }
    }.start()
  }

  override fun onCreate() {
    super.onCreate()
    FlutterCallkitIncomingPlugin.registerEventCallback(callkitEventCallback)

  }
}

Add this to your app level build file

   defaultConfig {
     
      ......
        //Replace with your package name e.g "com.example.app.MainApplication"
        manifestPlaceholders["applicationName"] = "com.example.flutter_callkit_incoming_example.MainApplication"

    }

Point ${applicationName} at your new class in Android manifest

 <application
        ....
        android:name="${applicationName}"
         <activity
        

Please check full example: Example

📋 Properties

Main Properties

Property Description Default
id UUID identifier for each call. UUID should be unique for every call and when the call is ended, the same UUID for that call to be used. Suggest using uuid. ACCEPT ONLY UUID Required
nameCaller Caller's name None
appName App's name. Used for display inside Callkit (iOS). App Name, Deprecated for iOS > 14, default using App name
avatar Avatar's URL used for display for Android. /android/src/main/res/drawable-xxxhdpi/ic_default_avatar.png None
handle Phone number/Email/Any None
type 0 - Audio Call, 1 - Video Call 0
duration Incoming call/Outgoing call display time (second). If the time is over, the call will be missed 30000
textAccept Text Accept used in Android Accept
textDecline Text Decline used in Android Decline
extra Any data added to the event when received {}
headers Any data for custom header avatar/background image {}
missedCallNotification Android data needed to customize Missed Call Notification Below
callingNotification Android data needed to customize Calling Notification Below
android Android data needed to customize UI Below
ios iOS data needed Below

Missed Call Notification

Property Description Default
subtitle Text Missed Call used in Android/iOS (show in missed call notification) Missed Call
callbackText Text Call back used in Android/iOS (show in missed call notification action) Call back
showNotification Show missed call notification when timeout true
isShowCallback Show callback action from missed call notification true

Calling Notification

Property Description Default
subtitle Text used in Android (show in calling notification) Calling...
callbackText Text used in Android (show in calling notification action) Hang Up
showNotification Show calling notification when start call/accept call true
isShowCallback Show hang up action from calling notification true

Android

Property Description Default
isCustomNotification Using custom notifications false
isCustomSmallExNotification Using custom notification small on some devices clipped out in Android false
isShowLogo Show logo app inside full screen. /android/src/main/res/drawable-xxxhdpi/ic_logo.png false
logoUrl Logo app inside full screen. Example: http://... https://... or "assets/abc.png" None
ringtonePath File name of a ringtone ex: ringtone_default. Put file into /android/app/src/main/res/raw/ringtone_default.mp3 system_ringtone_default using ringtone default of the phone
backgroundColor Incoming call screen background color #0955fa
backgroundUrl Using image background for Incoming call screen. Example: http://... https://... or "assets/abc.png" None
actionColor Color used in button/text on notification #4CAF50
textColor Color used for the text in full screen notification #ffffff
incomingCallNotificationChannelName Notification channel name of incoming call Incoming call
missedCallNotificationChannelName Notification channel name of missed call Missed call
isShowCallID Show call id app inside full screen/notification false
isShowFullLockedScreen Show full screen on Locked Screen (please make sure call requestFullIntentPermission for Android 14+) true

iOS

Property Description Default
iconName App's Icon. Used for display inside Callkit (iOS) CallKitLogo using from Images.xcassets/CallKitLogo
handleType Type handle call generic, number, email, Recommended to use generic for more reasonable callkit display generic
supportsVideo true
maximumCallGroups 2
maximumCallsPerCallGroup 1
audioSessionMode None, gameChat, measurement, moviePlayback, spokenAudio, videoChat, videoRecording, voiceChat, voicePrompt
audioSessionActive true
audioSessionPreferredSampleRate 44100.0
audioSessionPreferredIOBufferDuration 0.005
supportsDTMF true
supportsHolding true
supportsGrouping true
supportsUngrouping true
ringtonePath Add file to root project xcode /ios/Runner/Ringtone.caf and Copy Bundle Resources (Build Phases) Ringtone.cafsystem_ringtone_default using ringtone default of the phone

📁 Source Code

Please checkout repo GitHub:

📱 Pushkit - Received VoIP and Wake App from Terminated State (iOS Only)

Please check PUSHKIT.md for setup Pushkit for iOS.

📋 Todo

  • Run background / background execution support (implemented via self-managed Telecom service & didActivate background replay cache)
  • Swift Package Manager (SPM) support
  • Simplify the setup process
  • Custom notification for iOS (Missing notification)
  • Keep notification when calling

🎯 Demo

Demo Illustration

Images

Changelog

3.1.6

Hotfix release. Low-risk corrections only — no API changes.

Behaviour changes to be aware of

  • Android: ${applicationId}.PERMISSION_CALL is now signature instead of normal. Any app could previously request it and forge incoming/accept/decline broadcasts. Only affects setups where a separately-signed companion app sends these broadcasts.
  • Android: the incoming and ongoing notifications now use the app's own icon as the small icon. Previously an internal drawable always won, so the app icon could never appear.
  • Android: the ringtone now respects silent and vibrate ringer modes. It previously played in silent mode.
  • iOS: minimum deployment target raised to 15.0 (podspec was 10.0, Package.swift was 13.0). Xcode 26/27 refuse any target below 15.0 outright, so 3.1.5 could not be built with current Xcode at all. Apps still on an older Xcode that need iOS 13/14 should stay on 3.1.5. If your own Podfile/Xcode project is still below 15.0 you must raise it too, and you may need a post_install hook forcing IPHONEOS_DEPLOYMENT_TARGET for dependencies that declare very old targets — the example app's Podfile shows the pattern.

Fixes

  • iOS: ACTION_CALL_DECLINE stopped being emitted, and incoming calls stopped timing out, after the first outgoing call in a process. The internal call-state slots were never cleared.
  • iOS: timedOutPerforming resolved the call by the action's own identifier instead of CXCallAction.callUUID, so it never matched and onTimeOut never fired. It also fulfilled/failed a timed-out action, which CXProvider forbids.
  • iOS: the ringtone condition used || where && was meant, setting an empty ringtoneSound on the provider configuration.
  • iOS: removed 2602 trailing NUL bytes from SwiftFlutterCallkitIncomingPlugin.swift, which made grep/rg treat the file as binary and skip it. Line endings normalised to LF.
  • iOS: the podspec pinned VALID_ARCHS to x86_64 for the simulator, breaking arm64 simulator builds on Apple Silicon. Now uses EXCLUDED_ARCHS.
  • iOS: added PrivacyInfo.xcprivacy declaring the UserDefaults required-reason API (CA92.1). Verified to ship on both integration paths — inside flutter_callkit_incoming.framework under CocoaPods, and as a resource bundle under Swift Package Manager.
  • iOS: podspec version now tracks the package version instead of being pinned at 0.0.1.
  • Android: an unknown method channel call returned without submitting a result, leaving the Dart Future pending forever. Replies are now routed through a guard so a throw after a successful reply can no longer raise Reply already submitted.
  • Android: registerPhoneAccount could throw on the plugin-attach path and crash the host app at startup.
  • Android: the incoming-call screen acquired a wake lock and never released it, keeping the screen on for the full ring duration even after the call was answered or declined.
  • Android: incomingChannelEnabled() had an operator-precedence bug that reported channels as enabled on pre-Android-8 devices even when notifications were disabled.
  • Android: the accept action's label fell back to the default when textDecline was empty rather than textAccept.
  • Android: the incoming-call screen's dynamic receiver was registered as RECEIVER_EXPORTED for an app-private action.
  • Android: unregisterReceiver is now guarded against IllegalArgumentException.
  • Android: added android:showWhenLocked to the incoming-call activity; several OEMs honour only the manifest attribute.
  • Android: dropped the unused androidx.localbroadcastmanager dependency.

3.1.5

3.1.4

3.1.3

3.1.2

3.1.1

  • Reorganize iOS native source directory to support Swift Package Manager (SPM) by placing all source files inside the package root (ios/flutter_callkit_incoming/Classes/).
  • Resolve SPM mixed-language constraint by removing Objective-C wrappers and implementing a pure-Swift registrar class @objc(FlutterCallkitIncomingPlugin) to ensure seamless CocoaPods backward compatibility.
  • Modernize example app's Objective-C imports in AppDelegate.m using Clang module import syntax @import flutter_callkit_incoming;.

3.1.0

3.0.0

2.5.8

  • Fix OnGoing notification Android
  • Add missed call notification for iOS(notification/callback action - need to setup more in AppDelegate.swift)
  • Add requestNotificationPermission for iOS

2.5.7

  • Fix build Android
  • Fix stop sound notification when screen off Android

2.5.6

  • Fix bug duplicate permission Android
  • Replaces MediaPlayer with Ringtone Android
  • Fix bug notification channel Android
  • Stop sound notification when screen off Android

2.5.5

  • Fix bug duplicate permission Android

2.5.4

  • Fix bug custom permission Android
  • Remove calling notification when task remove
  • Fixed calling notification show
  • Fixed some bugs.

2.5.3

2.5.3-alpha

2.5.2

2.5.1

2.5.0

  • update jvmToolchain(17) for Android

2.0.4+2

2.0.4+1

  • Removed Telecom Framework (Android)

2.0.4

  • Removed Telecom Framework (Android)
  • Fixed hide notification for action CallBack (Android)

2.0.3

  • Fixed linked func hideCallkitIncoming

2.0.2+2

  • Fixed linked func hideCallkitIncoming

2.0.2+1

  • Fixed linked func hideCallkitIncoming

2.0.2

  • Add func hideCallkitIncoming clear the incoming notification/ring (after accept/decline/timeout)
  • Add props isShowFullLockedScreen on Android
  • Fixed example/Fixed update android 14

2.0.1+2

  • Add Action for onDecline
  • Add Action for onEnd
  • add android props isShowCallID

2.0.1+1

  • Add Callback AVAudioSession for WebRTC setup
  • Fix issue no audio for using WebRTC

2.0.1-dev.2

  • Add Action for onAccept

2.0.1-dev.1

  • Add AVAudioSession Appdelegate(iOS)

2.0.1-dev

  • Add AVAudioSession Appdelegate(iOS)

2.0.1

2.0.0+2

  • Fixed some bugs.
  • Support request permission for Android 13+ requestNotificationPermission

2.0.0+1

  • Fixed some bugs.
  • Add landscape for tablet
  • Fix issue head-up for redmi / xiaomi devices

2.0.0

1.0.3+3

  • Update README.md
  • Fixed some bugs.

1.0.3+2

  • REMOVED

1.0.3+1

1.0.3

  • REMOVED

1.0.2+2

  • Fix notification Android 12
  • Fix sound notification
  • Support backgroundUrl using path assets
  • Fixed some bugs.

1.0.2+1

  • Issue no audio when Accept(iOS)
  • Duplicate sound notification(Android)
  • Support Flutter 3
  • Fixed some bugs.

1.0.2

  • Fixed issue open app(terminated/background state - Android).
  • Completed Example
  • Fixed some bugs.

1.0.1+8

  • Add props isShowMissedCallNotification using show Missed call notification(Android)
  • Fixed issue decline(terminated/background state - there will be about 3 seconds to call the api before the app is closed.)
  • Fixed some bugs.

1.0.1+7

  • Fixed issue open app(terminated/background state).
  • Fixed some bugs.

1.0.1+6

  • Add props for text
  • Fixed issue open app(terminated/background state).
  • Fixed some bugs.

1.0.1+5

  • Update custom miss call notification
  • Fixed issue open app(terminated/background state).

1.0.1+4

  • add showMissCallNotification only for Android, using show miss call notification
  • Fixed some bugs.

1.0.1+3

  • add props isShowCallback using show Callback for miss call(Android)
  • public props data call for Object-C/Swift
  • Example using FCM(Android)
  • Fixed some bugs.

1.0.1+2

  • Fixed issue default ringtone(Android)
  • Fixed issue vibration(Android)
  • Fixed issue sound play type ringtone volumn system(Android)
  • Fixed flow incomming screen(Android)
  • Fixed some bugs.

1.0.1+1

  • Switch using Service for Ringtone(Android)
  • Fixed issue vibration(Android)
  • Add getDevicePushTokenVoIP() feature
  • Fixed some bugs.

1.0.1

  • Pustkit and VoIP setup instructions (PUSHKIT.md)
  • Callback from Recent History IOS
  • Using System ringtone for default
  • Fixed func endAllCalls() Android
  • Bugs Android 12.
  • Fixed some bugs.

1.0.0+8

  • Share func call from native(iOS)

1.0.0+7

  • Add custom headers using for avatar/background image (only for Android)

1.0.0+6

  • Fixed func activeCalls() Get active calls

1.0.0+5

  • Fixed endCall
  • Bugs Targeting Android 12 (Android).
  • Bugs audio session (iOS)
  • Fixed some bugs.

1.0.0+4

  • Update README.md.
  • Add func activeCalls() Get active calls
  • Remove notification when click action Call back (Android).
  • Bugs no activation of the audio session (iOS)
  • Fixed some bugs.

1.0.0+3

  • Update README.md.
  • Add props android isShowLogo.
  • Fixed some bugs.

1.0.0+2

  • Update README.md.
  • Update documentation.

1.0.0+1

  • Update README.md.
  • Fixed some bugs.

1.0.0

  • Initial release.

Installing

export PUB_HOSTED_URL=https://pub.mirror.p-hatan.com
flutter pub add flutter_callkit_incoming
pubspec.yaml
name: flutter_callkit_incoming
description: Flutter Callkit Incoming to show callkit screen in your Flutter app.
version: 3.1.6
homepage: https://github.com/hiennguyen92/flutter_callkit_incoming
repository: https://github.com/hiennguyen92/flutter_callkit_incoming
issue_tracker: https://github.com/hiennguyen92/flutter_callkit_incoming/issues
screenshots:
  - description: "lock screen"
    path: images/image6.jpg

false_secrets:
  - /example/**

environment:
  sdk: ">=3.0.0 <4.0.0"
  flutter: ">=3.10.0"


dependencies:
  flutter:
    sdk: flutter
  json_annotation: ^4.9.0

dev_dependencies:
  build_runner: ^2.7.0
  flutter_lints: ^6.0.0
  flutter_test:
    sdk: flutter
  json_serializable: ^6.10.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:
  # This section identifies this Flutter project as a plugin project.
  # The 'pluginClass' and Android 'package' identifiers should not ordinarily
  # be modified. They are used by the tooling to maintain consistency when
  # adding or updating assets for this project.
  plugin:
    platforms:
      android:
        package: com.hiennv.flutter_callkit_incoming
        pluginClass: FlutterCallkitIncomingPlugin
      ios:
        pluginClass: FlutterCallkitIncomingPlugin

  # To add assets to your plugin package, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  #
  # For details regarding assets in packages, see
  # https://flutter.dev/assets-and-images/#from-packages
  #
  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # To add custom fonts to your plugin package, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts in packages, see
  # https://flutter.dev/custom-fonts/#from-packages

topics:
  - callkit
  - incoming
  - flutter-callkit-incoming

Version history

VersionPublishedArchiveSHA-256
1.0.0On demandDownload785e7389…a60f25
1.0.0+1On demandDownload6bfb9af7…d4dcca
1.0.0+2On demandDownload5c7ad337…965af4
1.0.0+3On demandDownloadc03fde9a…4cd7bb
1.0.0+4On demandDownload50178719…844905
1.0.0+5On demandDownloada721d23d…0017e5
1.0.0+6On demandDownload4e720e0f…0191a2
1.0.0+7On demandDownloadc74332fe…b07188
1.0.0+8On demandDownloade1e8626e…fb422c
1.0.1On demandDownload78bf7eff…2b5f63
1.0.1+1On demandDownload8d7bee7a…338f32
1.0.1+2On demandDownload8c8d9c78…c0f7f1
1.0.1+3On demandDownload8d3012ca…107359
1.0.1+4On demandDownloada6fea4fa…0d9a53
1.0.1+5On demandDownload9a3da2fe…0160a6
1.0.1+6On demandDownload16d5901d…35eedb
1.0.1+7On demandDownloadf9ce8de9…208095
1.0.1+8On demandDownloadea25c18f…c8e7f4
1.0.2On demandDownload37f5c27f…4943d3
1.0.2+1On demandDownloadb5f31fa8…d77845
1.0.2+2On demandDownloade689c7f6…c95666
1.0.3On demandDownloadd6f7ec8f…968a7d
1.0.3+1On demandDownload0a07848d…f289eb
1.0.3+2On demandDownloadb5df19ac…7247f2
1.0.3+3On demandDownload8e76be35…a4a464
2.0.0On demandDownload46074904…9986f0
2.0.0+1On demandDownloadb71dcc1c…71a4dd
2.0.0+2On demandDownload2d2f2e9a…141b22
2.0.1-devOn demandDownloadc1571b8b…dc9718
2.0.1-dev.1On demandDownloadad041377…d4c1fe
2.0.1-dev.2On demandDownload4b41cccc…87b9c0
2.0.1On demandDownloadbb7cdaf2…f452ce
2.0.1+1On demandDownloada6316f16…cbbe69
2.0.1+2On demandDownloadccfc2cb0…1efbe4
2.0.2On demandDownloadf96b747d…d192e6
2.0.2+1On demandDownloadaa092520…09db24
2.0.2+2On demandDownload4028e482…7eee7a
2.0.3On demandDownload877363b5…eb6ac0
2.0.4On demandDownload6a3ae7d0…c6a031
2.0.4+1On demandDownload911978c1…3f6cef
2.0.4+2On demandDownload516f188b…ea922c
2.5.0On demandDownloadc1e90252…90f7fc
2.5.1On demandDownloada743285f…47b2e4
2.5.2On demandDownload21d645f0…29c239
2.5.3-alphaOn demandDownload6e01e68c…96d5be
2.5.3On demandDownloadef9d5a76…ada7a7
2.5.4On demandDownload9f6076c1…5ce959
2.5.5On demandDownloadf9cd4be8…fe3be8
2.5.6On demandDownload9efe234b…2a1c7f
2.5.7On demandDownloadf56301e1…183ea2
2.5.8On demandDownload993fb0f0…5ec9cd
3.0.0On demandDownload3589deb8…bbf9b8
3.1.0On demandDownload314d9df6…a50e86
3.1.1On demandDownload83beeb7b…32a014
3.1.2On demandDownload5e49358e…cae373
3.1.3On demandDownload0e418f2c…443b92
3.1.4On demandDownloadff548e3f…febfd1
3.1.5On demandDownloada7ca7800…a96341
3.1.6Cached · Upstream SHA-256 matchedDownload2d47db2e…c58318

Dependencies

flutter { "sdk": "flutter" }

json_annotation "^4.9.0"