flutter_callkit_incoming icon indicating copy to clipboard operation
flutter_callkit_incoming copied to clipboard

Not working in ios termenated state

Open avadhkatrodiya98 opened this issue 2 years ago • 16 comments

If iOS app killed or not in back stack or in terminate state then call kit not working

avadhkatrodiya98 avatar Apr 12 '23 06:04 avadhkatrodiya98

iOS app (ver 14.7.1): When app in background/terminated, app stopped. Apps receving VoIP pushes must post an incoming call (via CallKit or IncomingCallNotifications) in the same run loop as pushRegistry:didReceiveIncomingPushWithPayload:forType:[withCompletionHandler:] without delay. *** Assertion failure in -[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes], PKPushRegistry.m:353 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push callback.'

tinhnvc-gadget avatar Apr 13 '23 08:04 tinhnvc-gadget

This happens for us too across all iOS versions we have tested so far with flutter sdk v3.7.0 and flutter_callkit_incoming v1.0.3+3.

VijaiCPrasad avatar Apr 24 '23 02:04 VijaiCPrasad

This happens for us too across all iOS versions we have tested so far with flutter sdk v3.7.0 and flutter_callkit_incoming v1.0.3+3.

Hi bro, You can fix the error in the following way:

  1. Push CallKit from native (didReceiveIncomingPushWith method)
  2. Connect SIP (Flutter) I use sip_ua package
  3. In CXAnswerCallAction and CXEndCallAction invoke Flutter

In native (Swift)

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        print("Call answer from CallKit")
        flutterMethodChannel?.invokeMethod("acceptCall", arguments: "")
        action.fulfill()
    }
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        print("Call ended from CallKit")
        flutterMethodChannel?.invokeMethod("declineCall", arguments: "")
        action.fulfill()
}

In Flutter:

const pushCallKitChannel = IosMethodChannel.pushCallKitChannel;
    if (callState.state == CallStateEnum.CALL_INITIATION) {
      if (Platform.isIOS) {
        pushCallKitChannel.setMethodCallHandler((handler) async {
          if (handler.method == 'acceptCall') {
            handleAccept();
            OneContext().push(
              MaterialPageRoute(
                builder: (context) => CallingScreen(helper, call),
              ),
            );
          }

          if (handler.method == 'declineCall') {
            handleHangup();
          }
        });
      }
    }

tinhnvc-gadget avatar Apr 24 '23 02:04 tinhnvc-gadget

Thank for your reply we implement it and test it

On Mon, Apr 24, 2023 at 8:11 AM tinhnvc-gadget @.***> wrote:

This happens for us too across all iOS versions we have tested so far with flutter sdk v3.7.0 and flutter_callkit_incoming v1.0.3+3.

Hi bro, You can fix the error in the following way:

Push CallKit from native (didReceiveIncomingPushWith method) 2.

Connect SIP (Flutter) I use sip_ua package 3.

In CXAnswerCallAction and CXEndCallAction invoke Flutter In native (Swift) ` func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { print("Call answer from CallKit") flutterMethodChannel?.invokeMethod("acceptCall", arguments: "") action.fulfill() }

func provider(_ provider: CXProvider, perform action: CXEndCallAction) { print("Call ended from CallKit") flutterMethodChannel?.invokeMethod("declineCall", arguments: "") action.fulfill() }In Flutter: const pushCallKitChannel = IosMethodChannel.pushCallKitChannel;

if (callState.state == CallStateEnum.CALL_INITIATION) { if (Platform.isIOS) { pushCallKitChannel.setMethodCallHandler((handler) async { if (handler.method == 'acceptCall') { handleAccept(); OneContext().push( MaterialPageRoute( builder: (context) => CallingScreen(helper, call), ), ); }

  if (handler.method == 'declineCall') {
    handleHangup();
  }
});

} }`

— Reply to this email directly, view it on GitHub https://github.com/hiennguyen92/flutter_callkit_incoming/issues/270#issuecomment-1519300636, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5JLYR2J7JUC7IB3HNIAWF3XCXR6XANCNFSM6AAAAAAW3GN27A . You are receiving this because you authored the thread.Message ID: @.***>

avadhkatrodiya98 avatar Apr 24 '23 04:04 avadhkatrodiya98

Hello @avadhkatrodiya98, the solution provide by @tinhnvc-gadget worked for you? Can you provide more detail about how can be implemented? Please!

DavidGomezOv avatar May 02 '23 21:05 DavidGomezOv

iOS app (ver 14.7.1): When app in background/terminated, app stopped. Apps receving VoIP pushes must post an incoming call (via CallKit or IncomingCallNotifications) in the same run loop as pushRegistry:didReceiveIncomingPushWithPayload:forType:[withCompletionHandler:] without delay. *** Assertion failure in -[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes], PKPushRegistry.m:353 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push callback.'

I have the same issue in flutter_callkit_incoming version 2.0.0. @hiennguyen92

posawatji avatar May 25 '23 07:05 posawatji

I found a solution. In some cases, you don't want to show pushKit from VoIP such as rejected calls.

ref: https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry

completion()

  • The notification's completion handler. Execute this block when you finish processing the notification.

image

image

I use SwiftFlutterCallkitIncomingPlugin.sharedInstance.showCallkitIncoming to report incoming calls completion to block and let PushKit know you are finished.

this code from my case

    // Handle incoming pushes
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        print("didReceiveIncomingPushWith")
        guard type == .voIP else { return completion() }
        
        let uuid = UUID().uuidString
        let id = payload.dictionaryPayload["id"] as? String ?? uuid
        let nameCaller = payload.dictionaryPayload["name"] as? String ?? ""
        let action = payload.dictionaryPayload["action"] as? String ?? ""
        
        var info = [String: Any?]()
        info["id"] = id
        info["nameCaller"] = nameCaller
        info["handle"] = "generic" // phoneNum/email/generic
        info["type"] = 0 //isVideo set 1. not video set 0
        info["platform"] = "ios"
        info["duration"] = 60000
        info["supportsVideo"] = true
        info["maximumCallGroups"] = 1
        info["maximumCallsPerCallGroup"] = 1
        info["audioSessionMode"] = "default"
        info["audioSessionActive"] = true
        info["audioSessionPreferredSampleRate"] = 44100.0
        info["audioSessionPreferredIOBufferDuration"] = 0.005
        info["supportsDTMF"] = false
        info["supportsHolding"] = false
        info["supportsGrouping"] = false
        info["supportsUngrouping"] = false
        info["ringtonePath"] = "system_ringtone_default"
        
        let data = flutter_callkit_incoming.Data(args: info)
        
        //data.iconName = ...
        //data.....
        
        let appState = getAppState()
        
        let notificationData = payload.dictionaryPayload
       notificationData["id"] = id
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: "method_channel", binaryMessenger: controller.binaryMessenger)
        let args: [String: Any] = [
            "notificationData": notificationData,
            "appState":"\(appState)"
        ]
        channel.invokeMethod("CALLING", arguments: args)
 
        SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
        completion();
    }
// Get App state
enum AppState {
    case active
    case background
    case inactive
    case terminated
    case unknown
}

  // Get App state
    func getAppState() -> AppState {
        if UIApplication.shared.responds(to: #selector(getter: UIApplication.shared.backgroundRefreshStatus)) {
            // Check if the app is being terminated due to a system shutdown
            if UIApplication.shared.backgroundRefreshStatus == .denied && UIApplication.shared.applicationState == .background {
                return .terminated
            }
        }
        
        switch UIApplication.shared.applicationState {
        case .active:
            return .active
        case .background:
            return .background
        case .inactive:
            return .inactive
        @unknown default:
            return .unknown
        }
    }

I hope this solution will help you 🙂.

posawatji avatar May 26 '23 05:05 posawatji

I found a solution. In some cases, you don't want to show pushKit from VoIP such as rejected calls.

ref: https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry

completion()

  • The notification's completion handler. Execute this block when you finish processing the notification.

image

image

I use SwiftFlutterCallkitIncomingPlugin.sharedInstance.showCallkitIncoming to report incoming calls completion to block and let PushKit know you are finished.

this code from my case

    // Handle incoming pushes
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        print("didReceiveIncomingPushWith")
        guard type == .voIP else { return completion() }
        
        let uuid = UUID().uuidString
        let id = payload.dictionaryPayload["id"] as? String ?? uuid
        let nameCaller = payload.dictionaryPayload["name"] as? String ?? ""
        let action = payload.dictionaryPayload["action"] as? String ?? ""
        
        var info = [String: Any?]()
        info["id"] = id
        info["nameCaller"] = nameCaller
        info["handle"] = "generic" // phoneNum/email/generic
        info["type"] = 0 //isVideo set 1. not video set 0
        info["platform"] = "ios"
        info["duration"] = 60000
        info["supportsVideo"] = true
        info["maximumCallGroups"] = 1
        info["maximumCallsPerCallGroup"] = 1
        info["audioSessionMode"] = "default"
        info["audioSessionActive"] = true
        info["audioSessionPreferredSampleRate"] = 44100.0
        info["audioSessionPreferredIOBufferDuration"] = 0.005
        info["supportsDTMF"] = false
        info["supportsHolding"] = false
        info["supportsGrouping"] = false
        info["supportsUngrouping"] = false
        info["ringtonePath"] = "system_ringtone_default"
        
        let data = flutter_callkit_incoming.Data(args: info)
        
        //data.iconName = ...
        //data.....
        
        let appState = getAppState()
        
        let notificationData = payload.dictionaryPayload
       notificationData["id"] = id
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: "method_channel", binaryMessenger: controller.binaryMessenger)
        let args: [String: Any] = [
            "notificationData": notificationData,
            "appState":"\(appState)"
        ]
        channel.invokeMethod("CALLING", arguments: args)
 
        SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
        completion();
    }
// Get App state
enum AppState {
    case active
    case background
    case inactive
    case terminated
    case unknown
}

  // Get App state
    func getAppState() -> AppState {
        if UIApplication.shared.responds(to: #selector(getter: UIApplication.shared.backgroundRefreshStatus)) {
            // Check if the app is being terminated due to a system shutdown
            if UIApplication.shared.backgroundRefreshStatus == .denied && UIApplication.shared.applicationState == .background {
                return .terminated
            }
        }
        
        switch UIApplication.shared.applicationState {
        case .active:
            return .active
        case .background:
            return .background
        case .inactive:
            return .inactive
        @unknown default:
            return .unknown
        }
    }

I hope this solution will help you 🙂.

The solution didn't work for me. The app would still crash on iOS after ending the call with log saying the call wasn't reported callkit.

I also have a issue open here already. https://github.com/hiennguyen92/flutter_callkit_incoming/issues/197

VijaiCPrasad avatar Jun 07 '23 16:06 VijaiCPrasad

sorry this happened. you may need to add this after showIncoming. https://github.com/hiennguyen92/flutter_callkit_incoming/blob/6cb1ba422f835fc2482cb5c012cba1ecdb237188/example/ios/Runner/AppDelegate.swift#L78

for sure you can add this DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { completion() }

hiennguyen92 avatar Jun 09 '23 17:06 hiennguyen92

Can you please share a solution for rejecting a call from an incoming call(talker) before accepting a call via pushKit? @hiennguyen92

posawatji avatar Jun 12 '23 02:06 posawatji

Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push callback.

@hiennguyen92 thanks you but unfortunately calling completion() did not fix it. I still get "Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push callback." error and the app crashes

VijaiCPrasad avatar Jun 14 '23 03:06 VijaiCPrasad

Stuck with same issue? any update on this issue? . I am using ^1.0.3+3 version

Allamprabhu1058 avatar Nov 20 '23 06:11 Allamprabhu1058

Stuck with same issue? any update on this issue? flutter_callkit_incoming: ^2.0.4

flutter-vrinsoft avatar May 10 '24 09:05 flutter-vrinsoft

Stuck with same issue? any update on this issue? flutter_callkit_incoming: ^2.0.4+1

anmayorquin avatar Jul 19 '24 18:07 anmayorquin

Stuck with the same issue any updates ?

MuhammedMohsen1 avatar Jul 23 '24 11:07 MuhammedMohsen1