Skip to main content
iOS CallKit only works on a real device, not the simulator. The CallKit framework is not supported in the iOS Simulator.
Always test CallKit functionality on a physical iOS device. The simulator will not display incoming call UI.
Confirm your build target is set to a physical iOS device before running your app.
There are a few things to check:
  1. Ensure isShowFullLockedScreen is set to true in your AndroidParams:
android: const AndroidParams(
  isShowFullLockedScreen: true,
  // ...
)
  1. On Android 14+, you must explicitly request the full-screen intent permission before showing the call screen:
await FlutterCallkitIncoming.requestFullIntentPermission();
You can also check whether the permission is already granted:
await FlutterCallkitIncoming.canUseFullScreenIntent();
  1. Ensure your MainActivity uses singleInstance launch mode in AndroidManifest.xml:
<activity
    android:name=".MainActivity"
    android:launchMode="singleInstance">
Without singleInstance, the call screen may not appear correctly over the lock screen.
Custom ringtones must be placed in the correct directory and referenced by name without the file extension.
  1. Place your MP3 file at:
/android/app/src/main/res/raw/<ringtone_name>.mp3
  1. Set ringtonePath to the filename without the .mp3 extension:
android: const AndroidParams(
  ringtonePath: 'ringtone_default', // corresponds to ringtone_default.mp3
)
  1. To use the device’s default system ringtone instead, set:
android: const AndroidParams(
  ringtonePath: 'system_ringtone_default',
)
system_ringtone_default is the default value and uses whatever ringtone the user has set on their device.
Android 13 (API 33) requires the POST_NOTIFICATIONS runtime permission. You must call requestNotificationPermission before calling 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."
});
Skipping this step on Android 13+ will silently prevent incoming call notifications from appearing.
Alternatively, you can use requestPermission from the firebase_messaging package if you are already using Firebase Cloud Messaging.
When using PushKit, you must call completion() at the end of the pushRegistry(_:didReceiveIncomingPushWith:for:completion:) delegate method. Failing to do so causes iOS to terminate the app.
func pushRegistry(
    _ registry: PKPushRegistry,
    didReceiveIncomingPushWith payload: PKPushPayload,
    for type: PKPushType,
    completion: @escaping () -> Void
) {
    // Show the incoming call
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)

    // Always call completion — use asyncAfter if CallKit needs more time
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
        completion()
    }
}
Not calling completion() in the PushKit delegate will cause the system to crash your app the next time a VoIP push is received.
When using WebRTC, you need to manually manage the RTCAudioSession by enabling and disabling it in response to the CallKit audio session callbacks.In your AppDelegate.swift, implement the didActivateAudioSession and didDeactivateAudioSession callbacks provided by CallkitIncomingAppDelegate:
func didActivateAudioSession(_ audioSession: AVAudioSession) {
    RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
    RTCAudioSession.sharedInstance().isAudioEnabled = true
}

func didDeactivateAudioSession(_ audioSession: AVAudioSession) {
    RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
    RTCAudioSession.sharedInstance().isAudioEnabled = false
}
Also ensure that in application(_:didFinishLaunchingWithOptions:) you configure the audio session for manual use:
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
See the iOS setup guide and the AppDelegate.swift example for the full configuration.
If you use ProGuard or R8 for release builds, plugin classes may be obfuscated or removed, causing runtime crashes.Add the following rule to your proguard-rules.pro file:
-keep class com.hiennv.flutter_callkit_incoming.** { *; }
This prevents ProGuard from removing or renaming any class in the plugin package.
Starting from v2.5.0, the plugin requires Java SDK 17 or higher. If you see a build error related to JVM toolchain or Java version, update your build.gradle:
kotlin {
    jvmToolchain(17)
}
Also ensure your local Java installation (used by Gradle) is version 17+. You can verify with:
java -version
Using Java 11 or lower with v2.5.0+ will cause a build-time error.
Missed call notifications (and showing notifications while the app is in the foreground) require UNUserNotificationCenter delegate setup in AppDelegate.swift.Ensure you have the following in application(_:didFinishLaunchingWithOptions:):
if #available(iOS 10.0, *) {
    UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
And implement the corresponding delegate methods:
// Show notification when app is in foreground
override func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
    CallkitNotificationManager.shared.userNotificationCenter(
        center,
        willPresent: notification,
        withCompletionHandler: completionHandler
    )
}

// Handle callback action tapped in missed call 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()
}
See the iOS setup guide and the AppDelegate.swift example for full configuration.
activeCalls() behaves differently per platform:
  • iOS: Returns the list of currently active calls managed by CallKit (only their id field is populated). If no call is currently active in CallKit — for example, after a call has ended — the list will be empty.
  • Android: Returns only the last call, regardless of its current state.
final calls = await FlutterCallkitIncoming.activeCalls();
// iOS example output when a call is active:
// [{"id": "8BAA2B26-47AD-42C1-9197-1D75F662DF78"}]
// Returns [] if no call is currently active in CallKit.
Do not rely on activeCalls() to retrieve full call metadata on iOS. Store call parameters in your own app state when the call is initiated.

Build docs developers (and LLMs) love