I'm developing an Swift iOS 14 app that send and recieves push notifications from Firebase Cloud Messaging.
From FCM I send a message with a payload that must be treated by the app, updating an internal SQLite database with the payload data for later, show items in a view.
When the app is in Foreground, I recieved the notification in the didReceiveRemoteNotification method and update the database but when the app is in Background or killed, the notification is recieved but no one method is called to handle the payload and update de database.
I've read many topics about this problem but in none have I come to find a solution.
At the moment I don't want to use an external database to insert the data, and later read the external database, but if there is no other options i will change the app (the reason is that i don't want to store any information out of the user application).
My AppDelegate.swift is the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Firebase Auth + APNs
FirebaseApp.configure()
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
UserDefaults.standard.set(fcmToken, forKey: UserConstants.MESSAGING_TOKEN)
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if(userInfo["name"] != nil) {
ContactsService.sharedInstance.addAlert(phoneNumber: userInfo["phone"] as! String, name: userInfo["name"] as! String, isLocalized: Bool(userInfo["isLocalized"] as? String), longitude: (userInfo["longitude"] as! NSString).doubleValue, latitude: (userInfo["latitude"] as! NSString).doubleValue)
}
}
Can someone help me, telling me if it's possible to do in that way or it's necessary to store the data externally to later retrieve it?
Thank you!
There are two types of push notifications, alert notifications and background notifications. Alert notifications allow you to deliver visible alerts that can be interacted with in ways that your app can customize.Background notifications allow your application to fetch data from the background, upon receiving push notifications. Background notification should be used to keep your application up to date, even if the application isn't running. Also as of ios 10(and above)instead of using the didReceiveRemoteNotification method you can use didReceive method for handling the alert notifications.
Now coming back to your question in case of the alert notification the didReceive/didReceiveRemoteNotification method is called when the application is in the foreground or when the user taps on the application. Since, you want to update the database you can use the background notifications instead of the alert notification as it will automatically raise your application even when it is in background and will also call the didReceiveRemoteNotification:fetchCompletionHandler. while sending a background push notification make sure you :
Edit Info.plist and check the "Enable Background Modes" and "Remote notifications" check boxes.
Add "content-available":1 to your push notification payload, otherwise the app won't be woken if it's in the background
The notification’s POST request should contain the apns-push-type header field with a value of background, and the apns-priority field with a value of 5.
For more info please refer :
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
Related
I have used Twilio Chat SDK for my iOS app. The notifications when new message is added to the channel is coming up fine. But it doesn't update the app's notification badge count. I have attached the screenshot for push notification configuration in Twilio Portal.
Also when I ask for user authorization for notification,
the options set are:
let options: UNAuthorizationOptions = [.badge, .sound, .alert]
Your notification payload should look like the following to update app badge count:
{
"aps" : {
"alert" : "You got notificaiton.",
"badge" : 5
}
}
When this payload is received the badge count updates to 5.
Update: As mentioned in the Twilio documentation:
To update badge count on an application icon, you should pass badge count from the Chat Client delegate to the application:
func chatClient(_ client: TwilioChatClient, notificationUpdatedBadgeCount badgeCount: UInt) {
UIApplication.shared.applicationIconBadgeNumber = Int(badgeCount)
}
Note: But, this only works when the App is active. To update the badge count when the App is inactive you need to configure notification payload to the above-mentioned format from via a custom Twilio server, default configuration doesn't allow this.
When the app is inactive you can use this method to append the badge count:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if UIApplication.shared.applicationState != .active {
UIApplication.shared.applicationIconBadgeNumber += 1
}
}
I am using firebase push notification, where my app is subscribed to a topic, all is good. But I want to know if it is possible to show the notification if pass a notification. this is my scene:
local_user_id = 10
var payload = {
notification: {
title: "hi",
body: "this is a notification",
sound: "default"
},
data: {
user_id: "1",
message: "you should pay $3020.25"
}
};
1) control if user is_login (true/false)
2)get the message data of notification and check:
if (payload.data.user_id = local_user_id && is_login){
show_notification()
}
3) show notification
Actually I only have the notification and no more, I am new with firebase, this is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
}
application.registerForRemoteNotifications()
FirebaseApp.configure()
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
let dict = userInfo["aps"] as! NSDictionary
let message = dict["alert"]
print("response")
print(message)
}
I don't know how to do that what I want, is it posible?
thanks in advance
You can not control show hide notification in your application. You can put some logic on the backend side, whether this notification should be displayed or not. Nevertheless, I have workaround below possible way.
Use a silent push. Then trigger local notifications. Note: Silent
push isn't always reliable.
So just include content-available: 1 in your payload as shown
below to get a silent notification.it will act as silent notification.
Also in Info.plist should have UIBackgroundModes set to
remote-notification
but it'll be limited to Running and background mode only. you won't be able to receive or handle it if content-available is set to 0 while your app is offline
If you are trying to just present the notification to the user while the app is running in the foreground, you would need to have your AppDelegate conform to the UNUserNotificationCenterDelegate. This is because when the application is running, the notifications will be presented to the UNUserNotificationCenter shared object.
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void {
let content = notification.request.content
if content.data["user_id"] == local_user_id && is_login {
completionHandler(.alert)
} else {
completionHandler([])
}
}
}
You would want to make sure that the completionHandler is executed at some point in this block, because this is the handler that does the presentation of the notification. If you want a silent notification, you can use completionHandler([]) to silence the alert. Other possible options for the completionHandler are available here.
Scenarios:
1) Silent or Normal payload when App's in Foreground:
Nothing happens
2) Silent payload when App's in Background:
Nothing happens
3) Normal payload when App's in Background:
If User click the notification to open the App.
triggers the application:didReceiveRemoteNotification:fetchCompletionHandler
If user open the App clicking the App icon:
Nothing happens
These are the payloads I'm using for the APNs:
Normal payload: .
{
"aps":{
"alert":"andre test",
"badge":0,
"sound":"default",
"content-available":1
},
"acme-syncalarm":"true"
}
Silent payload: .
{
"aps":{
"content-available":1
},
"acme-syncalarm":"true"
}
I've implemented the Remote Push Notification using this code:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Receeeeeeeeived: \(userInfo)")
UIApplication.shared.applicationIconBadgeNumber = 11
completionHandler(.newData)
}
I also implemented this to check if the App is recovering from a kill state (as I've read in some Questions too), but the code never enters the print(rn)line.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let rn = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] {
print(rn)
}
}
EDIT 1 -
I have also enabled Remote Notifications in background mode for the App.
What do I have to do to cover the "Nothing Happens" Scenarios? 1 , 2 and 3.2 ?
Some notes:
"If user open the App clicking the App icon: Nothing happens" <-- That's expected, because you didn't interact with any notification directly. Imagine if you had 5 notifications arrived. How would you know which notification you should process...
normal payload won't have any key named content-available. So that again is a silent notification. Can you first see my answer here?
Some suggestions:
Make sure you've enabled Remote Notifications in background mode. Like this:
Additionally See here. iOS 11 initial releases were buggy for silent notifications. Make sure you have the latest version for your testing, otherwise it won't work. If you have an iOS 10 device, then first try testing with that...
Make sure you have Background App refresh and notifications available on your device. To see how to do it, refer to my linked answer.
Are you creating the payload yourself or you're using FireBase? If you're using Firebase then some of the keys change...and you must adjust accordingly.
make sure you've set some object as the delegate of UNUserNotificationCenterDelegate e.g.:
UNUserNotificationCenter.current().delegate = delegateObject
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let content = notification.request.content
// Process notification content
completionHandler([.alert, .sound, .badge]) // Display notification as regular alert and play sound
}
Code copied from here.
If you don't do such then you won't be showing any notification when the app is in the foreground. This should resolve the issue of when app is in foreground and you've received a normal remote notification.
I am attempting to send a simple push notification from the firebase notification console to a specific device using an FCM token. The firebase notification console shows the notification as sent but the device does not receive it. I have tried sending the notification and then waiting to see if the console logs from didReceiveRemoteNotification, but the notification takes too long (hours) to be shown as sent in the firebase console (even when I set the priority to high).
App Delegate
import UIKit
import Firebase
import FirebaseStorage
import FirebaseDatabase
import FirebaseMessaging
import CoreData
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Use Firebase library to configure APIs
FirebaseApp.configure()
/////
// For Firebase Cloud Messaging (FCM)
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()
// End of [for Firebase Cloud Messaging (FCM)]
/////
return true
}
///////////////////////
// FCM Setup
// Monitor token generation for FCM: Be notified whenever the FCM token is updated
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
}
// Monitor token generation for FCM:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
} // Handle messages received through the FCM APNs interface
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
print("didReceiveRemoteNotification")
// 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 message ID.
// gcm_message_id
if let messageID = userInfo["gcmMessageIDKey"] {
print("Message ID: \(messageID)")
}
^My guess is that the issue may have to do with the "gcm_message_id"/"gcmMessageId"/"gcm.message_id" as it is different in each of the three approaches I tried below
// Print full message.
print(userInfo)
}
// Handle messages received through the FCM APNs interface
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("didReceiveRemoteNotification (withCompletionHandeler)")
// 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 message ID.
if let messageID = userInfo["gcmMessageIDKey"] {
print("Message ID: \(messageID)")
}
^My guess is that the issue may have to do with the "gcm_message_id"/"gcmMessageId"/"gcm.message_id" as it is different in each of the three approaches I tried below
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
// End of [FCM Setup]
///////////////////////
}
View Controller
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Retrieve the current registration token for Firebase Cloud Messaging (FCM)
let token = Messaging.messaging().fcmToken
print("FCM token: \(token ?? "")")
}
}
Entitlements & Enabling Push Notifications
I have added the push entitlements and enabled background modes for push notifications and added the GoogleService-Info.plist to my project.
Method for sending Notification
I am creating notification from the Firebase Notifications console (as shown below) so there should be no issue with the structure of the notification itself.
I have tried the following approaches to remedy the issue, but all have produced the same result:
Google Firebase Documentation
Remote Notifications w/ firebase tutorial
Google Firebase Quickstart
Does anyone know why the notification is being marked as sent in the firebase notification console but not showing up on the device?
A couple of troubleshooting steps I use when working with push notifications are:
Get push working independent of the firebase services first. I use this tool.
Make sure that you dont have any bundle identifier namespace collisions. So for example having any combination of appstore build, testflight build, and / or develop build of an app on the device. Delete all but one instance of the app. The bundle identifier is how your device knows which app to route the push notification to.
When all else fails - I try to isolate the issue by building a new sample project and hook it up to a new firebase project and see if I can narrow my focus down to just being able to get push working without any other business logic in my app. This helps me prove to my self that I haven't gone insane, and proves to me that it's not some mysterious network condition leading to my woes.
I hope this helps you as you work get it all figured out.
Try to add the following code to didRegisterForRemoteNotificationsWithDeviceToken func:
Messaging.messaging().apnsToken = deviceToken
So it will look like this:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
It is work for me.
Does anyone know why the notification is being marked as sent in the firebase notification console but not showing up on the device?
Because "sent" does not mean "received".
Notifications cannot be guaranteed to be received on the device. With basic APNS infrastructure you even cannot get the information if a notifications was received or processed on the device.
If you don't receive a successfully sent message on the device there can be many reasons. Furthermore, even if you receive a Firebase token, that does not mean that your device can receive the notification in any case.
To isolate the problem I would suggest to build up the minimal setup and use APNS without Firebase. You could use Terminal or NWPusher (https://github.com/noodlewerk/NWPusher) for sending notifications from your local macOS system and the iOS native remote push notifications framework for receiving notifications.
Keep care to convert the APNS device token to the correct format required for submitting a notification:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.hexEncodedString()
print("Token: \(token)")
}
Data extension:
extension Data {
func hexEncodedString() -> String {
return map { String(format: "%02hhx", $0) }.joined()
}
}
I see that you have done everything in your project's Capabilities.
Some points:
Conform your class to messaging delegate like so:
Messaging.messaging().delegate = self
Make sure you've setup your certificates properly and uploaded everything on Firebase Push Notification Configurations (not exact term).
I've done several applications that uses Firebase Push Notification Service and making a sample app from scratch might help you figure out what you've been doing wrong.
And... here's a nicer code block for registering your application for push notification.
// Setup Push Notifications
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge]) { (granted, error) in
if error == nil{
DispatchQueue.main.async(execute: {
application.registerForRemoteNotifications()
})
}
}
}
else {
let notificationTypes: UIUserNotificationType = [.sound, .alert, .badge]
let notificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: nil)
application.registerForRemoteNotifications()
application.registerUserNotificationSettings(notificationSettings)
}
I have integrated push notification through GCM everything is working fine. But I am not getting notification message and sound. And the function didReceiveNotification: called in app delegate. And also not getting in background state.
Before making any comment or downvote consider following things.
I assume you have configured App Identifier in Developer portal, if not visit Apple Developer center
You have generated required provisional Profile & Certificate from Apple Developer Portal. If not visit App Distribution Guide
Make sure you have configured your bundle identifier correctly as defined in Apple Developer portal.
Following answer guides to configure APNS using your custom backend to send Push Notifications not for FireBase/GCM. To configure it using Firebase or GCM(As Firebase Cloud Messaging (FCM) is the new version of GCM) follow Google documentation
If all the above things are configured correctly then follow below steps:
Step 1: Register for APNS with Appropriate settings in didFinishLaunchingWithOptions inside AppDelegate file
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let pushNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
return true
}
Step 2: Add delegate methods to handle success or failure for APNS registration by adding following delegate methods
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
// Convert binary Device Token to a String (and remove the <,> and white space charaters).
var deviceTokenStr = deviceToken.description.stringByReplacingOccurrencesOfString(">", withString: "", options: nil, range: nil)
deviceTokenStr = deviceTokenStr.stringByReplacingOccurrencesOfString("<", withString: "", options: nil, range: nil)
deviceTokenStr = deviceTokenStr.stringByReplacingOccurrencesOfString(" ", withString: "", options: nil, range: nil)
print(deviceTokenStr);
// *** Store device token in your backend server to send Push Notification ***
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error)
}
Step 3: Now you have configured your APNS on device end, You can fire Push Notification from your server/backend, When Push Notification is received following method will be called when your app is in Foreground. Implement it into AppDelegate.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
print(userInfo)
}
To handle Push Notification while your application is in background (but not killed by removing from multitask) you need to take care of following things.
Make sure you have enabled Background Modes in Project Navigation->Targets->Capabilities->Turn on Background Modes and select Remote Notifications.
Now implement following method to handle Push Notification while in background. Make sure you handle UIBackgroundFetchResult properly.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
Note: If func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) method is implemented func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) method will not be called.
Read more about APNS in Apple Documentation.
Usually, iOS apps can receive push notifications via APNS not GCM and could not get any data when app is in background state. If iOS app gets push notification via APNS and it is in background state, the push notifications just shown in notification center & top of the screen with app's icon. If you see the notification, there's no problem with the server.
And there's no data arrived when app is in the background state, you should make your server api for the notifications data when the app is back on foreground state.