0

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.

enter image description here

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://code.tutsplus.com/tutorials/get-started-with-firebase-messaging-for-ios--cms-32126#comment-4345018783

https://www.iosapptemplates.com/blog/ios-development/push-notifications-firebase-swift-5

To summarize:

  • Got APN key, uploaded to Firebase
  • Turned on capabilities

enter image description here

  • 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 enter image description here

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.

enter image description here

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
          }

enter image description here

BVB09
  • 805
  • 9
  • 19
  • Hmm... did you upload an APNS certificate to Firebase? (which you get from the developer apple site) – Gabriel Pires Jun 28 '20 at 21:42
  • Hi @GabrielPires, no I went with the APNs authentication key instead. Posted an image at the bottom of the question. – BVB09 Jun 28 '20 at 21:46
  • Is that a typo? `didRegiterForRemoteNotificationsWithDeviceToken` – Gabriel Pires Jun 28 '20 at 21:51
  • Yes that is a typo. Good catch. I am surprised XCode didn't catch that. I tried this out with other delegate and datasource methods e.g. tableview, picker and text fields. Xcode caught all of those. I will compile and see if it works. – BVB09 Jun 29 '20 at 08:18

0 Answers0