hiennguyen92 / flutter_callkit_incoming

Flutter Callkit Incoming
https://pub.dev/packages/flutter_callkit_incoming
MIT License
171 stars 274 forks source link

Not working in ios termenated state #270

Open avadhkatrodiya98 opened 1 year ago

avadhkatrodiya98 commented 1 year ago

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

tinhnvc-gadget commented 1 year ago

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.'

VijaiCPrasad commented 1 year ago

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.

tinhnvc-gadget commented 1 year ago

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();
          }
        });
      }
    }
avadhkatrodiya98 commented 1 year ago

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:

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();
  }
});

} }`

— 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: @.***>

DavidGomezOv commented 1 year ago

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

posawatji commented 1 year ago

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 commented 1 year ago

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()

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 🙂.

VijaiCPrasad commented 1 year ago

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

hiennguyen92 commented 1 year ago

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() }

posawatji commented 1 year ago

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

VijaiCPrasad commented 1 year ago

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

Allamprabhu1058 commented 9 months ago

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

flutter-vrinsoft commented 4 months ago

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

anmayorquin commented 1 month ago

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

MuhammedMohsen1 commented 1 month ago

Stuck with the same issue any updates ?