Using topics with Firebase Cloud Messaging - ios

I am setting up Firebase Cloud Messaging to send notifications to an iOS app.
And I want to be able to send notifications, to all users who accepted to receive them.
After reading and experimenting quite a bit, my understanding is that (for my use case) I should set up some kind of general topic and then send each notification to this topic.
My question is: how to create a topic first, and then how to register (a client) to a topic in my iOS Swift app?
Though I tried to browse the net for info on that, I did not find much.
For information, I am using Xcode Version 10.1, iOS 12.1 and Swift 4.2.

Step 1: Setting Up Firebase
If you have yet to add Firebase to your project, it's all documented here:
https://firebase.google.com/docs/ios/setup
You will need to start the configuration of Firebase. For my project, I started the configuration in the AppDelegate upon app start.
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Messaging.messaging().delegate = self
...
}
...
}
Step 2: Requesting Authorization
Assuming you have all the APNs Authentication Key/Certificates configured on your Apple Developer Portal and Firebase Cloud Messaging settings,
https://developer.apple.com/account/ios/certificate/
https://console.firebase.google.com/u/0/project/FIREBASE_PROJECT_NAME/settings/cloudmessaging/ios:APP_BUNDLE_ID
Next, you will need to request authorization for Push Notifications on the device. I've placed this in one of the first few UIViewControllers my project.
class FirstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { (success, error) in
guard success else { return }
UIApplication.shared.registerForRemoteNotifications()
})
}
}
Step 3: Device Token
Upon registering for remote notification in step 2 with:
UIApplication.shared.registerForRemoteNotifications()
You will need to implement this delegate function in your AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
...
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
}
...
}
Do note that the deviceToken received is in NSData, and the apnsToken required by Firebase is in String.
Step 4: Topic Subscription
Remember the delegate reference we set to AppDelegate in step 1?
Messaging.messaging().delegate = self
You will need to implement its delegate function to let the app know that it Firebase did receive the token and it is ready to subscribe to a topic.
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
...
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
Messaging.messaging().subscribe(toTopic: "/topics/example")
}
...
}
Step 5: Validation
One simple way to check if your app has successfully subscribed to a topic is to send a push notification via Firebase Console.
https://console.firebase.google.com/u/0/project/FIREBASE_PROJECT_NAME/notification

Related

Flutter Firebase Messaging (Push Notifications) Not Working for iOS in an Already Existing Application

Firebase messaging works perfectly for Android, but the iOS build cannot receive notifications. I followed this tutorial to add push notifications to the application: https://www.youtube.com/watch?v=u-7ut-phOrA. The only difference is that I already had an App Identifier in the Apple Developer Portal. So instead of creating a new identifier, I enable push notification on the existing identifier.
The permissions are asked in the application as such:
void requestAndRegisterNotification() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
NotificationSettings settings =
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
provisional: false,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(
'Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
});
} else {
print('User declined or has not accepted permission');
}
}
The function was called in the InitState, and I can see the app is requesting permissions when testing.
The certificate in the Developer Portal looks like this: Certificate. The Indentifier looks like this: Identifier. The Key for the push notification looks like this: Key. The Firebase Cloud Messasing APNs looks like this: Firebase.
The AppDelegate file in the Runner has been modified to this:
import UIKit
import Flutter
import Firebase
import FirebaseMessaging
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
}
}
I'm not able to understand where the problem is coming from as there is no clear error message anywhere. The Android build works as expected, but iOS is not even recieving the push notification with a physical device. Even when I try to send the notification to a specific FCM token directly from Firebase.
according to Firebase documentation Enable the Background fetch and the Remote notifications background execution modes

pushRegistry:didUpdate:pushCredentials not being called

Looked at past questions about this but the answers to those have already been applied to my project but the pushRegistry() delegate method isn't getting invoked.
First here's the code:
import UIKit
import PushKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate{
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let notificationSettings = UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil)
application.registerUserNotificationSettings(notificationSettings)
UNUserNotificationCenter.current().delegate = self
UIApplication.shared.registerForRemoteNotifications()
let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
voipRegistry.desiredPushTypes = Set([PKPushType.voIP])
voipRegistry.delegate = self;
return true
}
...
}
extension AppDelegate: PKPushRegistryDelegate {
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
NSLog("voip token: \(pushCredentials.token)")
}
The following app capabilities have been enabled:
- Push Notifications
- Background Modes
- Background Fetch
- Remote Notifications
I'm using Xcode 9.1 Beta which no longer has Voip as an explicit capability, apparently this is no longer needed and importing PushKit should suffice.
On the provisioning portal Push Notifications have been enabled for the app id.
(A Void Push cert has been generated but obviously can't use this to send the pushes yet)
apparently this is no longer needed and importing PushKit should suffice.
I read a couple of posts saying this but its not correct.
In order to get things working I had to manually edit the info.plist to add voip to the list of background modes.
<string>voip</string>
What are Apple playing at? Why have they removed this as a capability from Xcode if its still required?

Firebase Notification To Device with FCM Token Says Sent but not received

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)
}

Cannot receive push notification on iOS from Firebase 3.2.0 topics

I followed the tutorial by google on https://firebase.google.com/docs/notifications/ios/console-topics#receive_and_handle_topic_messages to subscribe to a Firebase topic on my iOS app.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
FIRMessaging.messaging().subscribeToTopic("/topics/Notifications")
let homeViewController = UINavigationController(rootViewController: HomeViewController())
UINavigationBar.appearance().translucent = false
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = homeViewController
window?.makeKeyAndVisible()
return true
}
However, when I send a topic push notification out from the Firebase console. I could not receive any push notifications. But when I send out push notification to user segment from the console, the push is working perfectly. When I check the Xcode console, I am seeing this FIRMessaging error.
2016-05-31 11:11:47.893: <FIRMessaging/WARNING> Cannot subscribe to topic: /topics/Notifications with token: (null)
I've tried to search for this error but have no luck finding anything. I am not sure if this is the problem that is causing my app to not receive any push from topics.
Does anyone have this issue and know how to solve it?
Looks like maybe you're calling subscribeToTopic too early.
First, before you set up any Firebase call, make sure you call
FIRApp.configure()
That will ensure that all Firebase services are properly set up and initialized.
Next, you're going to need to wait just a bit to subscribe to topics. Your client needs to first register your app with both APNs and FCM to ensure that it can receive notifications. That involves a network call, which means you can't subscribe to topics when your app first launches.
Instead, I'd recommend putting that code into your application:didRegisterUserNotificationSettings handler instead. Something like this:
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
NSLog(#"Hooray! I'm registered!");
[[FIRMessaging messaging] subscribeToTopic:#"/topics/cool_users"];
}
Edit: And the Swift version...
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
print("Hooray! I'm registered!")
FIRMessaging.messaging().subscribeToTopic("/topics/swift_fans")
}
The accepted solution did not work for me. The token is not always available when application:didRegisterUserNotificationSettings: is called.
For example if application is freshly installed and starts for the first time FIRInstanceID.instanceID().token() returns nil.
You need to make sure application calls subscribeToTopic: after the token is available.
I ended up with creating a helper object that enqueues subscribeToTopic:, unsubscribeFrom: calls and executes them in FIFO order after the token arrives.
class FIRMessagingHelper {
private let queue: OperationQueue
init() {
queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation(TokenReadyOperation())
}
func subscribeTo(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().subscribeToTopic(topic)
})
}
}
func unsubscribeFrom(topic: String) {
queue.addOperation {
OperationQueue.main.addOperation({
FIRMessaging.messaging().unsubscribeFromTopic(topic)
})
}
}
}
TokenReadyOperation waits until the token appears. AsynchronousOperation is used as the base class to minimize boilerplate.
class TokenReadyOperation : AsynchronousOperation {
override init() {
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(TokenReadyOperation.tokenRefreshed(notification:)),
name: .firInstanceIDTokenRefresh,
object: nil)
}
override func didStart() {
finishIfTokenAvailable()
}
private func finishIfTokenAvailable() {
guard FIRInstanceID.instanceID().token() != nil else { return }
markFinished()
}
/// Posted every time token changes
#objc private func tokenRefreshed(notification: Notification) {
finishIfTokenAvailable()
}
}
Few things to keep in mind:
App must call FIRApp.configure() or FIRApp.configureWithOptions(_:) prior making any Firebase calls (as Todd Kerpelman mentioned)
subscribeToTopic:, unsubscribeFrom: are not thread safe and must be executed on main thread
Topic names has to be in "/topics/*" format (as henmer mentioned)
Make sure to use different configuration plist for debug and App Store release of your app. See FIRApp.configureWithOptions(_:) documentation.
Date & Time should be current, otherwise the token may not be delivered.
Make sure to use the newest framework version. I had issues with notification delivery with the SDK released around January 2017.
My problem was not solved by calling subscribeToTopic after
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
instead it worked by calling subscribeToTopic after
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
this function get called when you get your push token from APNS not firebase.
Xcode 8.3.2
Swift 3.0

APNS Firebase Notification failed to fetch token

For Swift3 / iOS10 see this link:
ios10, Swift 3 and Firebase Push Notifications (FCM)
I'm trying to use the Firebase for Notifications and I integrated it exactly as described in the docs.
But I don't understand why is doesn't work. When I build my project I see this line:
2016-05-25 16:09:34.987: <FIRInstanceID/WARNING> Failed to fetch default token Error Domain=com.firebase.iid Code=0 "(null)"
This my AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
FIRDatabase.database().persistenceEnabled = true
var service: DataService = DataService()
service.start()
registerForPushNotifications(application)
application.registerForRemoteNotifications()
return true
}
func registerForPushNotifications(application: UIApplication) {
let notificationSettings = UIUserNotificationSettings(
forTypes: [.Badge, .Sound, .Alert], categories: nil)
application.registerUserNotificationSettings(notificationSettings)
}
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
if notificationSettings.types != .None {
application.registerForRemoteNotifications()
}
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
var tokenString = ""
for i in 0..<deviceToken.length {
tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
}
FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Unknown)
print("Device Token:", tokenString)
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
print("Message ID: \(userInfo["gcm.message_id"]!)")
// Print full message.
print("%#", userInfo)
}
I too had the same issue and nothing worked for me. But all you have to do is go to your firebase console and then find your project and goto its settings, there check in its cloud messaging tab and upload your .p12 certificate into that.
thats it! happy coding :)
1.Set Notification Observer in didFinishLaunchingWithOptions Method
2.And Set tokenRefreshNotification method then u get Token in this method.
See below Code
import Firebase
import FirebaseMessaging
override func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
NotificationCenter.default.addObserver(self,
selector: #selector(self.tokenRefreshNotification(notification:)),
name: NSNotification.Name.firInstanceIDTokenRefresh,
object: nil)
}
// NOTE: Need to use this when swizzling is disabled
public func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Sandbox)
}
func tokenRefreshNotification(notification: NSNotification) {
// NOTE: It can be nil here
let refreshedToken = FIRInstanceID.instanceID().token()
print("InstanceID token: \(refreshedToken)")
connectToFcm()
}
func connectToFcm() {
FIRMessaging.messaging().connectWithCompletion { (error) in
if (error != nil) {
print("Unable to connect with FCM. \(error)")
} else {
print("Connected to FCM.")
}
}
}
public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
print(userInfo)
}
1 - Have you correctly configured your certificates as specified in the google documentation ( I won't recall the process here, it is quite long... )?
( https://firebase.google.com/docs/cloud-messaging/ios/certs#configure_an_app_id_for_push_notifications )
2 - I've been through some difficulties when setting up FCM. Once I thought everything was ok but notifications were still not working, I've decided to completely remove the app from the phone, clean my build folder and reinstall the whole thing. After that, it was working.
3 - The app was receiving notifications, but I was still getting the "Failed to fetch default token..." message. It disappeared after a while. Don't ask me why!
This is not really a proper answer, I just share my experience because I know configuring notification is not easy and every clue is welcome. So maybe this one can help. Cheers :)
After trying all of the above (and anything I could find elsewhere), what resolves the problem for me is to move
let token = FIRInstanceID.instanceID().token()
to be called when pressing a button, and not on app loading.
I know it's probably not the most elegant solution, but it's good enough for debugging purposes.
Im guessing the token is not available immediately by the server, and takes some time to be generated.
The answers above cover most of the issue, but I had the same issue and I found the following info useful:
Firebase can 'rotate' (change) a user's FCM token at any time. This is the 128 character ID that your server will use to send the push notification to the device.
Firebase docs say best practice is to use a delegate to monitor for changes with the delegate callback method:
- (void)messaging:(nonnull FIRMessaging *)messaging didRefreshRegistrationToken:(nonnull NSString *)fcmToken
[Obj - C]
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String)
[Swift]
The delegate method should be called on every change, at which point you can update the record in your server.
Unfortunately that wasn't working for me, I had a delegate but the callback wasn't being invoked. So I had to resort to manually updating the token on each app launch (as suggested by #micheal chein above) as follows:
NSString *deviceToken = [FIRInstanceID instanceID].token; // Send this to your server
[Obj-C]
let token = FIRInstanceID.instanceID().token() // Send this to your server
[Swift]
** Important: update the token after a delay (20-25s), as the rotation can sometimes only reflect after some time. You can use a timer for this.
After this I still get the APNS warning/error message:
2017-06-06 09:21:49.520: <FIRInstanceID/WARNING> Failed to fetch APNS token Error Domain=com.firebase.iid Code=1001 "(null)"
BUT, push notifications work every time without fail. So I think that log message is a bit off (possibly mistimed). If you can get option 2 to work for you, definitely do that!
FCM was working for me then just stopped. I did what Rabs G. suggested and removed the app and installed again and the notifications started working again.

Resources