I am able to send test notifications from the Firebase console in the foreground and background (app is dormant AND closed). But push notifications that are triggered by the cloud functions are not being received.
I know the push notifications work because the Android version of the app is able to receive them.
Here are the tutorials I used to enable push notifications (I naturally excluded deprecated functions):
https://www.iosapptemplates.com/blog/ios-development/push-notifications-firebase-swift-5
To summarize:
- Got APN key, uploaded to Firebase
- Turned on capabilities
- I grabbed the REVERSED_CLIENT_ID from the Google services .plist file and pasted it as a URL scheme. I haven't the fainted clue what this is all about but it's here in this tutorial
This is my app delegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
let gcmMessageIDKey = "gcm.message_id"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
Messaging.messaging().delegate = self
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)
}
//get application instance ID
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instance ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
}
}
application.registerForRemoteNotifications()
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
extension AppDelegate : MessagingDelegate {
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 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) {
// 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)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
func application(_ application: UIApplication, didRegiterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { //data -> String in
return String(format: "%02.2hhx", $0)
}
print(tokenParts)
Messaging.messaging().apnsToken = deviceToken
Messaging.messaging().setAPNSToken(deviceToken, type: .unknown)
UserDefaults.standard.synchronize()
}
}
@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)")
}
// Print full message.
print(userInfo)
completionHandler()
}
}
I turned off swizzling (I cannot for the life of me grasp what it is but as per Firebase, I should turn it off so I did.
It's worth mentioning that the token wasn't always being saved on the server when I pushed it from AppDelegate. Since I have a side menu setup in the app, I placed this as an extension there (maybe I'll change it later but it's imperative that the cloud function has the latest token):
extension ContainerViewController : MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(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.
let db = Firestore.firestore()
getUserEmail { (userId) in
if let userId = userId {
let userRef = db.collection("Customer_Data").document(userId)
userRef.setData(["tokenId" : fcmToken], merge : true)
}
}
}
func getUserEmail(completion : @escaping (String?) -> Void) {
let user = Auth.auth().currentUser
if let user = user?.email {
completion(user)
}
}
}
So I can verify that the token is being saved. What could I possibly be doing wrong at this point? And yes, this is on a physical device that's plugged into the Mac AND when a build is pushed over the air.
Edit Added the APNs authentication key instead of the certificate
Payloads for the 2 notifications
data: {
title: notificationTypes[notificationType].title + ' ' + planType ,
body: 'Meal Plan: ' + order.mpName + '\n' +
'Restaurant:' + order.restaurantName + '\n' +
'Total sum :' + order.currentTotal,
}
And
data : {
title: notificationTypes[notificationType].title + ' ' + planType ,
body: 'Meal Plan: ' + order.mpName + '\n' +
'Restaurant:' + order.restaurantName + '\n' +
'Total sum :' + order.currentTotal,
color:'#3EDF3E',
tag: notificationTypes[notificationType].tag
}