16
votes

Using the newer Firebase cloud messaging with method swizzling i am successfully able to receive a payload in the didReceiveRemoteNotification method in my app delegate when my app is foregrounded. However i do not get any sort of payload and didReceiveRemoteNotification does not get called when my app is backgrounded, despite the api response that the message is successfully sent (see below)
Here is the request that i send to the FCM api to trigger a push notification https://fcm.googleapis.com/fcm/send

{
"to": "{valid-registration-token}",
"priority":"high", //others suggested setting priority to high would fix, but did not
  "notification":{
      "body":"Body3",
      "title":"test"  
 } 
}


with response from FCM

{
      "multicast_id": 6616533575211076304,
      "success": 1,
      "failure": 0,
      "canonical_ids": 0,
      "results": [
        {
          "message_id": "0:1468906545447775%a4aa0efda4aa0efd"
        }
      ]
    }

Here is my appDelegate code

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

        Fabric.with([Crashlytics.self])
        FIRApp.configure()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.tokenRefreshNotification),
                                                         name: kFIRInstanceIDTokenRefreshNotification, object: nil)
        return true
    }



    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
        // Let FCM know about the message for analytics etc.
        FIRMessaging.messaging().appDidReceiveMessage(userInfo)

        // Print full message.
        print("%@", userInfo)

        // handle your message

//        let localNotification = UILocalNotification()
//        localNotification.fireDate = NSDate()
//        let notification = userInfo["notification"]!
//        localNotification.alertBody = notification["body"] as? String
//        localNotification.alertTitle = notification["title"] as? String
//        localNotification.timeZone = NSTimeZone()
//        application.scheduleLocalNotification(localNotification)

    }

    func tokenRefreshNotification(notification: NSNotification) {
        let refreshedToken = FIRInstanceID.instanceID().token()!
        print("InstanceID token: \(refreshedToken)")
        LoginVC.registerForPushNotifications()
        connectToFcm()
    }


    //foreground messages
    func applicationDidBecomeActive(application: UIApplication) {
        connectToFcm()
    }

    // [START disconnect_from_fcm]
    func applicationDidEnterBackground(application: UIApplication) {
        FIRMessaging.messaging().disconnect()
        print("Disconnected from FCM.")
    }


    func connectToFcm() {
        FIRMessaging.messaging().connectWithCompletion { (error) in
            if (error != nil) {
                print("Unable to connect with FCM. \(error)")
            } else {
                print("Connected to FCM.")
            }
        }
    }

}

I call this code at a later flow in my app to ask for permissions

static func registerForPushNotifications() {
    let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
            UIApplication.sharedApplication().registerUserNotificationSettings(settings)
            UIApplication.sharedApplication().registerForRemoteNotifications()
}

Since i am able to receive notifications while app is foregrounded i assume this would allay all concerns that my apns certificates are not uploaded or that the registration token is incorrect. If that is not the case, please comment and i'll go down that rabbit hole again. There's probably something simple that i am overlooking, but how can i get the notifications to appear while the app is backgrounded? Thanks

7
I am facing a similar struggle today: the handler in app delegate is called, when the application is in the foreground, but no tray notification appears, when the app is in the background. Interesting to see if anything turns up. Will let you know if I figure this out. - Kazimieras
Have you taken a look at this issue on github? - Kazimieras
When your app is in the foreground you can receive messages directly from FCM not through APNs which is what is used when your app is in the background. To be sure that APNs is configured correctly could you confirm that you are receiving an APNs token by implementing the didRegisterForRemoteNotificationsWithDeviceToken method. - Arthur Thompson
@ArthurThompson Before this version of the code, i was not using the method swizzling technique and did implement the didRegisterForeRemoteNotificationsWIthDeviceToken method where i was able to get a token. At first i'd hoped i was done, but when this token didn't work for FCM i had to refactor. Does that answer your question? - Daniel George
@Kazimieras Yes i read the whole thing. I tried setting priority to high and content-available to true. but no luck. I also tried using both at the same time. but others have said that that will not work, and in my case that was again true. - Daniel George

7 Answers

12
votes

Apparently, (despite using method swizzling that should be doing it automatically) you must still set the APNS token on the Firebase Instance. Consequently this means you must also implement the didRegisterForRemoteNOtificationsWithDeviceToken method with something like

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {


        // set firebase apns token
        FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Unknown)
}

As an added note. Using "priority":"high" was useful for testing as it sent the notification right away.

3
votes

The desired behavior, at least for my app, is for the arrival of a push notification where the content of the PN (Push Notification) should be shown in a desired page in the app.

I've three different app states where a PN can arrive: (of course it depends on your implementation, but you should get an idea how apple processes the PN in these states)

(the behavior is determined by apple!)

1) the app is in foreground:

  • PN received
  • pops up in the Notification Center (you can either click on it or not - it does not matter)
  • badge will be updated
  • PN is available in the desired page

In this case the app always processes the PN

2) the app is in background:

  • PN received
  • pops up in the Notification Center (you can either click on it or not - it does not matter)
  • badge will be updated
  • PN is available in the desired page

In this case the app always processes the PN

3) the app is killed/closed:

  • PN received
  • pops up in the Notification Center
  • now there are three possible ways

    1. swipe notification away -> PN will be NOT processed by the app

    2. open the app via the launcher icon directly -> PN will be NOT processed by the app

    3. click on the notification -> PN will be processed by the app

For this case, if the user does not open the app by tapping the notification, there is no way to get the notification data.

To be independent of the app state, you need to setup a server, where you can request the specific data. E.g. you've a view which shows all notifications, you need an API to request specific data. You shouldn't process the data in the receiving method of the Pushnotification.

I hope that helps someone!

1
votes

Similarily to Daniel George's solution I could fix my problem with:

Messaging.messaging().apnsToken = deviceToken

FirebaseInstanceID 2.0.8, FirebaseMessaging 2.0.8

0
votes

Follows these step after getting successful token from FCM to enable the background payload data fetch using FCM:

  1. open Project Navigator > AppCapabilities > Background Modes > check for remote notifications

  2. In App.delegate define:

    func application(_ application: UIApplication,didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    
        print("userinfo in background mode on-\(userInfo)")
    
        completionHandler(UIBackgroundFetchResult.newData)
    }
    
  3. Format your payload correctly according to apple guidelines: enter link description here

  4. Do not forget to add key value pair: {"content-available":1} unless you won't able to fetch payload in background.

now try to send the notification again using FCM token. Have a happy coding

0
votes

Make sure that you have added "content_available" : true in the JSON payload. Otherwise you won't get push notification in the background mode.

"notification" : {
            "body" : "this is body",
            "title" : "this is title"
}
"content_available" : true
0
votes

I was struggling to understand why my iOS14 Notifications weren't going through. This is not mentioned on the Firebase Docs.

On iOS14, December 2020, I can confirm that @Daniel George's solution still stands and fixed my notifications from not coming through. Reposting with updated Syntax.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Messaging.messaging().apnsToken = deviceToken }

iOS14.1, Swift 5.3, Firebase and Firebase Cloud Messaging as up to date on Dec 18th 2020.

-2
votes

AFAIK, for ios you wont be able to use FCM for send push notification messages for apps in background or killed state.

Firebase Push Notification