How to get notification response data using Firebase? - ios

I have implemented FCM notification to send notification.
When I send a notification from the firebase console then I get the notification. But when I send the notification from the server, I get success but the notification doesn't appear..
Secondly, how do I get the notification response data?
My didReceiveRemoteNotification function is also not working. Here is my code:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
print("Recived: \(userInfo)")
print("success")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Recived: \(userInfo)")
print("success")
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("Recived: \(userInfo)")
print("success")
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
self.application(application, didReceiveRemoteNotification: userInfo) { (UIBackgroundFetchResult) in
print("Recived: \(userInfo)")
print("success")
}
print("Recived: \(userInfo)")
print("success")
let state: UIApplicationState = application.applicationState
// user tapped notification while app was in background
if state == .inactive || state == .background {
print("Inactive")
}
else {
print("Active")
}
}

I found that the message below works best for me both with Android and iOS (I use a firebase function to send it but that shouldn't matter). Note that for this to work you need to use the send method and not one of the deprecated ones, so something like: admin.messaging().send(message)
Message to feed to the send:
const message = {
token: addresseePushToken,
/*
Apple Push Notification Service
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns
*/
apns: {
headers: {
'apns-priority': 10, // defaults to 10 (send immediately), can be set to 5 (consider device power)
'apns-collapse-id': chatId // group multiple notifications into a single notification (must not exceed 64 bytes)
},
payload: {
aps: {
alert: {
title: title,
body: doc.message,
},
badge: unseenCount+1,
}
}
},
/*
Android specifics
https://firebase.google.com/docs/cloud-messaging/admin/send-messages#android_specific_fields
*/
android: {
priority: 'high', // either 'normal' or 'high' - high send immediately & waking sleeping device
notification: { // notification object creates status bar notification if app in background
tag: chatId, // key for grouping notifications (Android only)
title: title,
body: doc.message,
color: '#ffffff', // notification's icon color
icon: thumbUrl, // if not specified FCM displays the launcher icon as defined in app manifest
}
},
// data object is available to the app both when received in foreground and background
data: {
type: 'chat',
senderId: senderId,
title: title,
body: doc.message,
},
}

I solved an issue very similar to this adding this to the Firebase notification:
"apns" : {
"payload" : {
"aps" : {
"content-available" : 1
}
}
}

Related

Swift iOS app receive push notification when app is inactive and run code

Platform
Swift 5
iOS 13+
xCode 11
Node v14.2.0
Firebase/Firestore latest
Setting
Alice send push notification to Bob, while Bob's phone is .inactive or .background. Bob's phone should get notification and immediately trigger code.
Problem
This question has plenty of answers, but most of what I can find revolves around hacking the PushKit and CallKit native API to send .voIP pushes. Per this question (iOS 13 not getting VoIP Push Notifications in background), Apple no longer allow you to send .voIP pushes w/o triggering CallKit's native phone ring routine.
On iOS side, I have the following bits in AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
registerForPushNotifications()
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
{
print(">>> I would like this to be triggered even if the app is asleep")
switch application.applicationState {
case .active:
print(">>>>>>> the app is in [FOREGROUND]: \(userInfo)")
break
case .inactive, .background:
print(">>>>>>>> the app is in [BACKGROUND]: \(userInfo)")
break
default:
break
}
}
func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter
.current()
.requestAuthorization(options:[.alert, .sound, .badge]) {[weak self] granted, error in
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
guard settings.authorizationStatus == .authorized else { return }
Messaging.messaging().delegate = self
DispatchQueue.main.async {
// Register with Apple Push Notification service
UIApplication.shared.registerForRemoteNotifications()
/// cache token client side and save in `didRegisterForRemoteNotificationsWithDeviceToken`
if let token = Messaging.messaging().fcmToken {
self.firebaseCloudMessagingToken = token
}
}
}
}
//#Use: listen for device token and save it in DB, so notifications can be sent to this phone
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
if (firebaseCloudMessagingToken != nil){
self.updateMyUserData(
name : nil
, pushNotificationToken: firebaseCloudMessagingToken!
)
}
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
///print(">>> Failed to register: \(error)")
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// #NOTE: this fires when the app is open. So you can go the call screen right away
let payload = notification.request.content.userInfo as! [String:Any?]
let type = payload["notificationType"]
print(">> this fires if the app is currently open")
}
/// #NOTE: we are using backward compatible API to access user notification when the app is in the background
/// #source: https://firebase.google.com/docs/cloud-messaging/ios/receive#swift:-ios-10
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print(" this fires when the user taps on the notification message")
}
On the server/Node.js side, I send push notification this way:
// Declare app push notification provider for PushKit
const _ApnConfig_ = {
token: {
key : fileP8
, keyId : "ABCDEFG"
, teamId: "opqrst"
},
production: false
};
var apnProvider = new apn.Provider(_ApnConfig_);
exports.onSendNotification = functions.https.onRequest((request, response) => {
var date = new Date();
var timeStamp = date.getTime();
const deviceTok = "..."
var recepients = [apn.token( deviceTok )]
const notification = new apn.Notification();
notification.topic = "com.thisisnt.working"
notification.body = "Hello, world!";
notification.payload = {
from: "node-apn"
, source: "web"
, aps: {
"content-available": 1
, "data" : { "custom_key":"custom value", "custom_key_2":"custom value 2" }
}
};
notification.body = "Hello, world # " + timeStamp;
return apnProvider.send(notification, recepients).then(function(res) {
console.log("res.sent: ", res.sent)
console.log("res.failed: ", res.failed)
res.failed.forEach( (item) => {
console.log(" \t\t\t failed with error:", item.error)
})
return response.send("finished!");
}).catch( function (error) {
console.log("Faled to send message: ", error)
return response.send("failed!");
})
})
Both are pretty standard. I have set the content-availabe to 1. Right now the messages are coming through and displayed by Apple Push Notification center, they're just not triggering the block with didReceiveRemoteNotification as intended.
You need to enable the background mode - remote notifications capability.
To receive background notifications, you must add the remote notifications background mode to your app. In the Signing & Capability tab, add the Background Modes capability, then select the Remote notification checkbox.
Enabling the remote notifications background mode:
For watchOS, add this capability to your WatchKit Extension.
Source: Pushing Background Updates to Your App | Apple Developer Documentation

iOS handling Push notification tap when app is active

How can I handle push notification tap when app is active?
didReceiveRemoteNotification userInfo: [AnyHashable : Any] is called when iOS device receives a push notification and same function is called when user taps on the notification. How can I differentiate in this function how this function is called? I'm using OneSignal of push notification in case it's necessary to understand the problem.
OneSingal has closures which informs you about notifications and user actions. This is how I use that
func initOneSignalNotifications(withLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {
let onesignalInitSettings = [kOSSettingsKeyAutoPrompt: false]
OneSignal.initWithLaunchOptions(launchOptions, appId: Constants.oneSignalKey, handleNotificationReceived: { (receivedNotification) in
//Notification is received
}, handleNotificationAction: { (notificationResult) in
//Notification was tapped
}, settings: onesignalInitSettings)
OneSignal.inFocusDisplayType = OSNotificationDisplayType.notification;
OneSignal.promptForPushNotifications(userResponse: { accepted in
FileHandler.log(message: "Notification permission granted: \(accepted)", tag: .application, logLevel: .info)
})
}
Here inFocusDisplayType means when your app is open, OneSignal will still display a notification.
You can handle click in, If your target is above iOS 10.
func userNotificationCenter ( _ center : UNUserNotificationCenter, didReceive
response : UNNotificationResponse, withCompletionHandler completionHandler :
#escaping () -> Void ) {
// perform notification received/click action as per third party SDK as per their document
}
}
else, You need to manage it with flags in didReceiveRemoteNotification userInfo: [AnyHashable : Any].

didReceiveRemoteNotification function doesn't called with FCM notification server

I'm using FCM server for remote notifications, it worked perfectly with sending and receiving notifications.
My problem is when the device is in the background, this function func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) doesn't called.
Solutions that I've tried:
This function:
userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
is called when user tapped on the notification. Otherwise, it's not triggered.
Adding "content-available": true, "priority": "high" to the payload. Also, I tried this value "content-available": 1.
This is my function code:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("userInfo: \(userInfo)")
completionHandler(.newData);
}
Just adding on accepted answer: in my case the problem was the same, but as I'm sending the Notification through an http "POST" NSMUtableRequest the way to apply the accepted answer was to actually add it in the notification parameters together with sound, badge, tiles etc etc..
let postParams: [String : Any] = [
"to": receiverToken,
"notification": [
"badge" : 1,
"body": body,
"title": title,
"subtitle": subtitle,
"sound" : true, // or specify audio name to play
"content_available": true, // this will call didReceiveRemoteNotification in receiving app, else won't work
"priority": "high"
],
"data" : [
"data": "ciao",
]
]
Hope this will also help others, as I spent a long time trying to figure out how to apply this solution.
Many thanks.
If you are testing with Postman then try changing "content-available": true to "content_available" : true. content-available will send notification but it doesn't call didReceiveRemoteNotification.
All you need to see remote notification in background and foreground and show as alert when app run are this appDelegate.swift that i used in my code:
import Firebase
import UserNotifications
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let gcmMessageIDKey = "gcm_message_id"
var deviceID : String!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// [START set_messaging_delegate]
Messaging.messaging().delegate = self
// [END set_messaging_delegate]
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()
FirebaseApp.configure()
Fabric.sharedSDK().debug = true
return true
}
func showNotificationAlert(Title : String , Message : String, ButtonTitle : String ,window : UIWindow){
let alert = UIAlertController(title: Title, message: Message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: ButtonTitle, style: .cancel, handler: nil))
window.rootViewController?.present(alert, animated: true, completion: nil)
}
// [START receive_message] remote notification
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}
// This function is added here only for debugging purposes, and can be removed if swizzling is enabled.
// If swizzling is disabled then this function must be implemented so that the APNs token can be paired to
// the FCM registration token.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("APNs token retrieved: \(deviceToken)")
// With swizzling disabled you must set the APNs token here.
// Messaging.messaging().apnsToken = deviceToken
}
}
// [START ios_10_message_handling]
#available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
// 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)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
completionHandler([.alert, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
if let aps = userInfo["aps"] as? NSDictionary {
if let alert = aps["alert"] as? NSDictionary {
if let title = alert["title"] as? NSString {
let body = alert["body"] as? NSString
showNotificationAlert(Title: title as String, Message: body! as String, ButtonTitle: "Ok", window: window!)
}
} else if let alert = aps["alert"] as? NSString {
print("mohsen 6 =\(alert)")
}
}
// Print full message.
print(userInfo)
completionHandler()
}
}
// [END ios_10_message_handling]
extension AppDelegate : MessagingDelegate {
// [START refresh_token]
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
deviceID = fcmToken
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
// [END refresh_token]
// [START ios_10_data_message]
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set Messaging.messaging().shouldEstablishDirectChannel to true.
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("Received data message: \(remoteMessage.appData)")
}
// [END ios_10_data_message]
}
The first thing you need to check is the Background Mode > Remote Notification is on. If not, it will not be invoked in the background mode.
Step 1 – Add framework
import UserNotifications
Step 2 – Inherit AppDelegate with UNUserNotificationCenterDelegate
final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {}
Step 3 – Make appropriate changes for registerPushNotifications and call it didFinishLaunchingWithOptions
private func registerPushNotifications() {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.currentNotificationCenter().delegate = self
UNUserNotificationCenter.currentNotificationCenter().requestAuthorizationWithOptions([.Badge, .Sound, .Alert], completionHandler: { granted, error in
if granted {
UIApplication.sharedApplication().registerForRemoteNotifications()
} else {
// Unsuccessful...
}
})
} else {
let userNotificationTypes: UIUserNotificationType = [.Alert, .Badge, .Sound]
let settings = UIUserNotificationSettings(forTypes: userNotificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
}
}
Step 4 – Implement new handlers
// leave this for older versions
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
// For iOS10 use these methods and pay attention how we can get userInfo
// Foreground push notifications handler
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
if let userInfo = notification.request.content.userInfo as? [String : AnyObject] {
// Getting user info
}
completionHandler(.Badge)
}
// Background and closed push notifications handler
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
if let userInfo = response.notification.request.content.userInfo as? [String : AnyObject] {
// Getting user info
}
completionHandler()
}
As you tried i believe every thing , background permission is enable , content-available is the part of payload, you are also receiving notification in active state, last thing will be Power save mode, you need to check is power saving is off; as if you Phone is in power saving mode, then background refresh and other process is gone in limited state, so app will not call didReceivedNotifcation in background.

Silent push notifications still pop up as normal

I'm trying to setup silent push notifications and I'm stuck with this problem. JSON that I send to APNs is:
{
"aps": {
"alert": "test",
"badge": 0,
"content-available": 1
}
}
Delegate method is:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
var pushData = userInfo["aps"] as? [AnyHashable : Any];
if pushData?["content-available"] as? Int == 1 {
NSLog("received silent notification")
completionHandler(.noData)
} else {
NSLog("received notification")
completionHandler(.newData)
}
}
When backend sends push notification, my app is in background. XCode shows me 'received silent notification', but this notification still pops up as normal. Could you please tell me, what I'm doing is wrong? It probably shouldn't be happening, right?
Project is set up with 'remote notifications' checked in 'background modes'.
Correct json payload for silent push notification should look like this
{
"aps" = {
"content-available" : 1,
"sound" : ""
};
// add custom key-value pairs
}

didReceiveRemoteNotification not called Swift

For some reason my didReceiveRemoteNotification is never called. I have done the following:
checklist of APNS:
Create AppId allowed with Push Notification
Create SSL certificate with valid certificate and app id
Create Provisioning profile with same certificate and make sure to add device
With Code:
Register app for push notification
Handle didRegisterForRemoteNotificationsWithDeviceToken method
Set targets> Capability> background modes> Remote Notification
Handle didReceiveRemoteNotification
Yet my function does not seem to get called. My code looks like this:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if (application.respondsToSelector("isRegisteredForRemoteNotifications"))
{
// iOS 8 Notifications
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: (.Badge | .Sound | .Alert), categories: nil));
application.registerForRemoteNotifications()
}
else
{
// iOS < 8 Notifications
application.registerForRemoteNotificationTypes(.Badge | .Sound | .Alert)
}
return true
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
var tokenString = ""
for var i = 0; i < deviceToken.length; i++ {
tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
}
apnsID = tokenString
println("******apnsID is \(apnsID)")
dToken = deviceToken
println("******dToken is \(dToken)")
NSUserDefaults.standardUserDefaults().setObject(deviceToken, forKey: "deviceToken")
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
println("***********didFailToRegisterForRemoteNotificationsWithError")
println(error)
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
println("Getting notification")
var message: NSString = ""
var alert: AnyObject? = userInfo["aps"]
println(userInfo["aps"])
if((alert) != nil){
var alert = UIAlertView()
alert.title = "Title"
alert.message = "Message"
alert.addButtonWithTitle("OK")
alert.show()
}
}
add content_available:true in your request . for iOS payload data.You will get notification in didReceiveRemoteNotification. Now handle it.
As I found out having the same problem myself, in order for didReceiveRemoteNotificationto be called, the incoming notification music carry an "available_content" : true parameter with the notification payload. So in case of you sending the notification in a dictionary form , as was my case in device to device pushes, you just have to add it in the dictionary as you would specify other parameters as you would for sound or badge, also needed if you're using some sort of push service as Postman
Three steps:
Add logic here to handle incoming notification:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Add your logic here
completionHandler(.newData)
}
Add Background mode to capabilities in target, and ensure to check 'Remote notifications'
While sending a push notification, add "content_available" : true
This worked for me in iOS 14/Xcode 12.3.

Resources