i am working on e-commerce mobile app, of course i need to send push notifications with a specific order number or product , so once user click on the push notification it shall handle the custom data sent with push notification to open the product or order page.
so my issue is , when the application is fully terminated i am not able to capture the custom data i've sent from server, the push notification is received successfully but none of firebase delegates are triggered when app is terminated . unlike the foreground , when app is in foreground and user clicks on the push notification i am able to handle the custom data and able to open the product / order page .
any idea why i am not able to capture push notification when app is terminated and user clicked on the push to open the app ?
PHP Code (Backend) :
$msg = array
(
'body' => $push['text'],
'title' => $push['title'],
'sound' => 'default',
'vibrate' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$push['params'] = @json_decode($push['params']) ? json_decode($push['params']) : array();
$fields = array
(
'registration_ids' => array_values($push['tokens']),
'notification' => $msg,
'data' => $push['params']
);
$data_string = json_encode($fields);
$headers = array ( 'Authorization:key =' . $api, 'Content-Type: application/json' );
$ch = curl_init(); curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' );
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_POSTFIELDS, $data_string);
$result = curl_exec($ch);
curl_close ($ch);
Swift Code (App delegate extension which handle everything for push ) :
import Foundation
import FirebaseCore
import FirebaseMessaging
extension APP_delegate {
public func registerForPushNotification(){
// [START set_messaging_delegate]
Messaging.messaging().delegate = self
// [END set_messaging_delegate]
// Register for remote notifications. This shows a permission dialog on first run, to
// show the dialog at a more appropriate time move this registration accordingly.
// [START register_for_notifications]
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: { (granted, error) in
if granted {
// Register after we get the permissions.
UNUserNotificationCenter.current().delegate = self
}
}
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
LW_APP.shared.appDelegate.application.registerUserNotificationSettings(settings)
}
LW_APP.shared.appDelegate.application.registerForRemoteNotifications()
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("APNs token retrieved: \(deviceToken)")
Messaging.messaging().apnsToken = deviceToken
}
}
// [START ios_10_message_handling]
@available(iOS 10, *)
extension APP_delegate: UNUserNotificationCenterDelegate {
// [START receive_message]
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
print("[FCM][1] \(userInfo)")
UserDefaults.standard.set("asd", forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print full message.
print("[FCM][2] \(userInfo)")
UserDefaults.standard.set("asd", forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
completionHandler(UIBackgroundFetchResult.newData)
}
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions)
-> Void) {
let userInfo = notification.request.content.userInfo
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
// [START_EXCLUDE]
// Print message ID.
if let messageID = userInfo["gcm.message_id"] {
print("Message ID (1) : \(messageID)")
}
UserDefaults.standard.set("asd", forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
completionHandler([[.badge, .sound]])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// [START_EXCLUDE]
// Print message ID.
// if let messageID = userInfo["gcm.message_id"] {
// print("Message ID (2) : \(messageID)")
// }
// if let aps:[String:Any] = userInfo["aps"] as? [String:Any] {
// //The message
// print("Alert Data => \(aps)")
// }
UserDefaults.standard.set("asd", forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
if let custom_data:String = userInfo["gcm.notification.data"] as? String {
// The custom Data comes as json string need to decode it
// Env.pushData = Tools.fromJson(custom_data)
NotificationCenter.default.post(name: .pushAction, object: nil)
}
// [END_EXCLUDE]
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler()
}
}
// [END ios_10_message_handling]
extension APP_delegate: MessagingDelegate {
// [START refresh_token]
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(String(describing: fcmToken))")
if Env.sharedInstance.pushToken != fcmToken {
Env.sharedInstance.pushToken = fcmToken ?? ""
LW_APP.api.register_push_token(nil) { _ in }
}
print("Firebase registration token: \( Env.sharedInstance.pushToken )")
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: ["token": fcmToken ?? ""])
}
}
PS : When app is not terminated i am able to get the custom data inside the push notification when user clicks on the push notification using this method . unlike the background issue when app fully terminated.
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
UserDefaults.standard.set("asd", forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
if let custom_data:String = userInfo["gcm.notification.data"] as? String {
// The custom Data comes as json string need to decode it
// Env.pushData = Tools.fromJson(custom_data)
NotificationCenter.default.post(name: .pushAction, object: nil)
}
// [END_EXCLUDE]
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler()
}
i am stuck in this case for weeks , any advice much appreciated.
CodePudding user response:
In order to receive remote notifications you need to implement the notification extension in your app See IOS Documentation. Note that all extensions in IOS are isolated from your main app. So, actually you are going to build a mini application bundled together with your main app. From this extension you will receive all remote notifications if the main app is on background or killed by the user.
See also the following tutorial.
Please note that the main app and the extension can communicate only via shared groups. So when the user taps on a notification you can launch your app which will read the shared information in order to complete the job.
CodePudding user response:
So after fixing the issue , here is the full application delegate which has detailed comments for each delegate which might help someone else to understand better the FCM and its logic .
import Foundation
import FirebaseCore
import FirebaseMessaging
extension APP_delegate:MessagingDelegate {
//Call to ask user to register for push notification
public func registerForPushNotification(){
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)
LW_APP.shared.appDelegate.application.registerUserNotificationSettings(settings)
}
LW_APP.shared.appDelegate.application.registerForRemoteNotifications()
}
//Handle Token to be sent to server or saved
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("[FCM] token: \(String(describing: fcmToken))")
//My Token saved on server in case not matching the old saved token ( refresh )
if Env.sharedInstance.pushToken != fcmToken {
Env.sharedInstance.pushToken = fcmToken ?? ""
LW_APP.api.register_push_token(nil) { _ in }
}
//Sending notification to all other listners if case they want to save the token too
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: ["token": fcmToken ?? ""])
}
//Push notification recieved for Backgroubd process
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
completionHandler(UIBackgroundFetchResult.newData)
}
}
//For devices with iOS10 and
@available(iOS 10, *)
extension APP_delegate : UNUserNotificationCenterDelegate {
//when recieve push notification ( user not yet clicked on it ) ( only when app is in foreground )
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// Change this to your preferred presentation option
completionHandler([[.banner, .badge, .sound]])
}
//Register did succesfully
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}
//Register did fail
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}
//When user click on the push notification to open it ( When app is terminated or opened )
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID from userNotificationCenter didReceive: \(messageID)")
}
print(userInfo)
if let custom_data:String = userInfo["gcm.notification.data"] as? String {
// The custom Data comes as json string need to decode it
UserDefaults.standard.set(custom_data, forKey: "pushData")
UserDefaults.standard.synchronize()
NotificationCenter.default.post(name: .pushAction, object: nil)
}
completionHandler()
}
}