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