I am using FCM to create and send push notifications for my iOS app.
Dev Environment:
Xcode 11.3
iPhone X running iOS 13.3
Swift 5.2
Pod Versions:
- Firebase 6.14.0
- FirebaseMessaging 4.1.10
- FirebaseInstanceID 4.2.8
Problem:
Prior to running into an issue, I had setup my app to be able to receive notifications when the app was in both the background and the foreground. Very happy with myself I committed the code. After this point I have been unable to receive notifications in the foreground OR the background. Regardless of using whether the notification is sent from Cloud Messaging dashboard or POSTMAN, I receive a successful response but the notification never appears.
At first I thought I may have hit the notification quota but it is now 2 days post fact.
To troubleshoot I have tried:
- Uninstalled and re-installed the app (Which refreshes the device token)
- Moved
UIApplication.shared.registerForRemoteNotifications()
to beforeFirebaseApp.configure()
- Downloaded a fresh GoogleService-Info.plist and replaced existing
- Checked that bundle id's etc all match
- Updated firebase pods to latest (FirebaseMessaging was at 4.1.9 if that helps)
- Set
Messaging.messaging().shouldEstablishDirectChannel = true
- Removed and Re-added the needed capabilities
- Set
FirebaseAppDelegateProxyEnabled
to both YES and NO - Set
shouldEstablishDirectChannel = true
- Set
useMessagingDelegateForDirectChannel = true
- Moved some logic from didFinishLaunchingWithOptions() to applicationDidBecomeActive()
Code:
Note: This is the unaltered code that originally worked for me.
AppDelegate.swift
import UIKit
import Firebase
import FBSDKCoreKit
import GoogleMaps
import SwiftLocation
import GooglePlaces
import Crashlytics
import GoogleSignIn
import Armchair
import UserNotifications
import FirebaseMessaging
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var swipeNavigationViewController: SwipeNavigationViewController!
override init() {
super.init()
FirebaseApp.configure()
Database.database().isPersistenceEnabled = true
swipeNavigationViewController = SwipeNavigationViewController()
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseConfiguration.shared.setLoggerLevel(.error)
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
// Google Maps
GMSServices.provideAPIKey(FireBaseConstants.GoogleAPIKey)
GMSPlacesClient.provideAPIKey(FireBaseConstants.GoogleAPIKey)
GeocoderRequest.GoogleOptions(APIKey: FireBaseConstants.GoogleAPIKey)
let navigationViewController = UINavigationController(rootViewController: swipeNavigationViewController)
navigationViewController.setNavigationBarHidden(true, animated: false)
self.window?.rootViewController = navigationViewController
self.window?.makeKeyAndVisible()
showAlertIfPointedTowardProductionDatabase()
setupReviewRequest()
UIApplication.shared.registerForRemoteNotifications()
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
// If granted comes true you can enabled features based on authorization.
guard granted else { return }
DispatchQueue.main.async {
print("UserID: \(UserManager.sharedManager.currentUser?.userID)")
let pushManager = PushNotificationManager(userID: "currently_logged_in_user_id")
pushManager.registerForPushNotifications()
}
}
UNUserNotificationCenter.current().delegate = self
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let handledByFB = ApplicationDelegate.shared.application(app, open: url, options: options)
var handledByGoogle = false
if !handledByFB {
handledByGoogle = GIDSignIn.sharedInstance().handle(url)
}
let handled = handledByFB || handledByGoogle
return handled
}
private func setupReviewRequest() {
//Code...
}
// This method will be called when app received push notifications in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
}
PushNotificationManager.swift
import Foundation
import Firebase
import FirebaseFirestore
import FirebaseMessaging
import UIKit
import UserNotifications
class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate {
let userID: String
let gcmMessageIDKey = "gcm.message_id"
init(userID: String) {
self.userID = userID
super.init()
}
func registerForPushNotifications() {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { (_, error) in
guard error == nil else{
print(error!.localizedDescription)
return
}
}
//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)")
}
}
UIApplication.shared.registerForRemoteNotifications()
updateFirestorePushTokenIfNeeded()
}
func updateFirestorePushTokenIfNeeded() {
if let token = Messaging.messaging().fcmToken {
// let usersRef = Firestore.firestore().collection("users_table").document(userID)
// usersRef.setData(["fcmToken": token], merge: true)
print("Remote instance ID token: \(token)")
}
}
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.
}
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("Received data message: \(remoteMessage.appData)")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print(response)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
print(userInfo)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}
func application(_ application: UIApplication,didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { //data -> String in
return String(format: "%02.2hhx", $0)
}
Messaging.messaging().apnsToken = deviceToken
Messaging.messaging().setAPNSToken(deviceToken, type: .unknown)
UserDefaults.standard.synchronize()
}
}
This was setup using all of the following links (With a few others I forgot as well I'm sure):
- https://code.tutsplus.com/tutorials/get-started-with-firebase-messaging-for-ios--cms-32126#comment-4345018783
- Firebase - Swift 4: Message sent but not received
- Get push notification while App in foreground iOS
- https://www.iosapptemplates.com/blog/ios-development/push-notifications-firebase-swift-5
Response Info:
Postman:
{
"multicast_id": 2586780331808083728,
"success": 1,
"failure": 0,
"canonical_ids": 0,
"results": [
{
"message_id": "0:1578532253832479%2b1845e62b1845e6"
}
]
}
Cloud Messaging: