0

I am struggling to handle APN push notification in iOS in background and terminated state.

I have tried using FCM background handling service. But whenever a push notification is fired using APN Token it doesn't call _firebaseBackgroundHandler() method.

I have used another library 'flutter_apns' but it is not able to handle background push notification.

I don't know what I'm missing here.

AppDelegate.swift

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
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]
    
    if #available(iOS 10.0, *) {
           // For iOS 10 display notification (sent via APNS)
           UNUserNotificationCenter.current().delegate = self
           let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
           UNUserNotificationCenter.current().requestAuthorization(
                   options: authOptions,
                   completionHandler: {_, _ in })
       } else {
           let settings: UIUserNotificationSettings =
           UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
           application.registerUserNotificationSettings(settings)
       }
       application.registerForRemoteNotifications()
       return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    
}

// Call back from Recent history
override func application(_ application: UIApplication,
                          continue userActivity: NSUserActivity,
                          restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    
    guard let handleObj = userActivity.handle else {
        return false
    }
    
    guard let isVideo = userActivity.isVideo else {
        return false
    }
    let nameCaller = handleObj.getDecryptHandle()["nameCaller"] as? String ?? ""
    let handle = handleObj.getDecryptHandle()["handle"] as? String ?? ""
    let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    //set more data...
    data.nameCaller = "Johnny"
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.startCall(data, fromPushKit: true)
    
    return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}

// Handle updated push credentials
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
    print(credentials.token)
    let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
    print(deviceToken)
    //Save deviceToken to your server
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
    print("didInvalidatePushTokenFor")
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}
func application(application: UIApplication,  didReceiveRemoteNotification userInfo: [NSObject : AnyObject],  fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {

    print("Recived: \(userInfo)")

}

// Handle incoming pushes
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    print("didReceiveIncomingPushWith")
    guard type == .voIP else { return }
    
    let id = payload.dictionaryPayload["id"] as? String ?? ""
    let nameCaller = payload.dictionaryPayload["nameCaller"] as? String ?? ""
    let handle = payload.dictionaryPayload["handle"] as? String ?? ""
    let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false
    
    let data = flutter_callkit_incoming.Data(id: id, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
    //set more data
    data.extra = ["user": "abc@123", "platform": "ios"]
    //data.iconName = ...
    //data.....

    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data,fromPushKit: true)
}}

register() method of flutter_apn library in main.dart

final PushConnector connector = createPushConnector();

Future<void> _register() async {
final connector = this.connector;

connector.configure(
  onLaunch:  (onLaunch) async {
    print("on1 ==> ${onLaunch.data.toString()}");
    return await null;
  },

  onResume: (data) async { var responce = await onPush('onResumess', data);print("responce data ${responce}");},
  onMessage:  (onLaunch) async {
    print("on3 ==> ${onLaunch.data.length}");
    return await null;
  },

  onBackgroundMessage: _onBackgroundMessage,
);
connector.token.addListener(() {
  print('Token ${connector.token.value}');
});
connector.requestNotificationPermissions();

if (connector is ApnsPushConnector) {
  connector.shouldPresent = (x) async {
    final remote = RemoteMessage.fromMap(x.payload);
    return remote.category == 'MEETING_INVITATION';
  };
  connector.setNotificationCategories([
    UNNotificationCategory(
      identifier: 'MEETING_INVITATION',
      actions: [
        UNNotificationAction(
          identifier: 'ACCEPT_ACTION',
          title: 'Accept',
          options: UNNotificationActionOptions.values,
        ),
        UNNotificationAction(
          identifier: 'DECLINE_ACTION',
          title: 'Decline',
          options: [],
        ),
      ],
      intentIdentifiers: [],
      options: UNNotificationCategoryOptions.values,
    ),
  ]);
}}

here onPush() should be called whenever app is in background, or in terminated state.

onPush() method in main

Future<dynamic> onPush(String name, RemoteMessage payload) async {
await FlutterCallkitIncoming.endAllCalls();
SharedPreferences prefs = await SharedPreferences.getInstance();
var data = GetStorage();
storage.append('$name: ${payload.notification?.title}');

final action = UNNotificationAction.getIdentifier(payload.data);
print("Payload Data1 ${payload.notification!.title.toString()}");
print("Payload Data2 ${payload.notification!.body.toString()}");
print("Payload  ${payload.notification!.toMap()}");

if(payload.notification!.title!.contains('from')){
  prefs.setBool('incomingCall', true);
  data.write('callingName', payload.notification!.title.toString());
  showCallkitIncoming(Uuid().v4(),
   payload.notification!.title.toString(),
   payload.notification!.body.toString()
  );

} else {
  print('NOT IOS BG CALLING');
}

if (action != null && action != UNNotificationAction.defaultIdentifier) {
  storage.append('Action: $action');
}

return Future.value(true);}

_onBackgroundMessage() method in main.dart

Future<dynamic> _onBackgroundMessage(RemoteMessage data) async {
 await FlutterCallkitIncoming.endAllCalls();
 var responce = await onPush('onBackgroundMessage', data);
 print("BG responce data ${responce}");
 if(data.notification!.title!.contains('from')){
 showCallkitIncoming(Uuid().v4(),
  data.notification!.title.toString(),
  data.notification!.body.toString()
 );
 }else{
   print('NOT IOS BG CALLING');
 }
}

0 Answers0