Search code examples
iosswiftswiftuifirebase-cloud-messagingapple-push-notifications

SwiftUI remote push notifications without AppDelegate (Firebase Cloud Messaging)


I'm trying to implement remote push notifications in SwiftUI 2.0 and there is no AppDelegate. I know I can provide one via @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate but I have learned that it's not recommended.

I have tried triggering a notification via Firebase Cloud Messaging but I do no receive any test notifications. I just got the pop up to allow notifications and thats it.

I do not get any errors or something.. nothing really happens.

Am I missing something?

The test:

enter image description here

Firebase registration token: Optional("fwRsIKd7aUZeoLmmW5b4Zo:APA91bHrVvArS-mLZMEkdtzTxhRUuMWVgHNKXdLethAvR3Fa3h_RmAcdOz_jJzp1kDsEEtcvbnAFUn9eh9-cUSCTy9jBibbFoR2xngWdzWCvci1_iLQJtHtCjxk-C02CkVUDl7FX8esp")

Here is my code:

import SwiftUI
import Firebase
import OSLog

@main
struct Le_fretApp: App {
    @StateObject var sessionStore = SessionStore()
    @StateObject var locationManagerService = LocationManagerService()
    @StateObject var userViewModel = UserViewModel()
    
    var notificationsService = NotificationsService()
    
    
    
    init() {
        UIApplication.shared.delegate = NotificationsService.Shared
        FirebaseConfiguration.shared.setLoggerLevel(.min)
        
        notificationsService.register()
        
        FirebaseApp.configure()
        
        notificationsService.setDelegate()
    }
    
    var body: some Scene {
        WindowGroup {
            TabViewContainerView()
                .environmentObject(sessionStore)
                .environmentObject(userViewModel)
                .environmentObject(locationManagerService)
                .onAppear {
                    sessionStore.listen()
                    userViewModel.listen()
                }
        }
    }
}

The service:

import Foundation
import UserNotifications
import OSLog
import UIKit

import Firebase


class NotificationsService: NSObject, UNUserNotificationCenterDelegate {
    static let Shared = NotificationsService()
    let gcmMessageIDKey = "gcmMessageIDKey"
    
    func register() {
        // 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 })
        
        DispatchQueue.main.async {
          UIApplication.shared.registerForRemoteNotifications()
        }
    }
    

      // 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, .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)")
        }

        // With swizzling disabled you must let Messaging know about the message, for Analytics
        // Messaging.messaging().appDidReceiveMessage(userInfo)

        // Print full message.
        print(userInfo)

        completionHandler()
      }
}

extension NotificationsService: UIApplicationDelegate {
    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)
    }
}

extension NotificationsService: MessagingDelegate {
    func setDelegate() {
        Messaging.messaging().delegate = self
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      print("Firebase registration token: \(String(describing: 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.
    }
}

Solution

  • I just created an App Delegate. Works for local and remote notifications.

    I have a PushNotificationManager that does the remote pushing. Whenever I send data to Firebase (I'm using Firestore), I pass in the AppDelegate.fcmToken to the user's fcmToken property (every user has one in the model) e.g. token: user.fcmToken.

    class AppDelegate: NSObject, UIApplicationDelegate {
        
        private var gcmMessageIDKey = "gcm_message_idKey"
        static var fcmToken = String()
        
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            
            FirebaseApp.configure()
            Messaging.messaging().delegate = self
            UNUserNotificationCenter.current().delegate = self
            registerForPushNotifications()
            
            return true
        }
        
        func registerForPushNotifications() {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
                print("Permission granted: \(granted)")
                guard granted else { return }
                self?.getNotificationSettings()
            }
        }
        
        func getNotificationSettings() {
            UNUserNotificationCenter.current().getNotificationSettings { settings in
                print("Notification settings: \(settings)")
                guard settings.authorizationStatus == .authorized else { return }
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }
        
        func application(
            _ application: UIApplication,
            didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
           
            AppDelegate.fcmToken = deviceToken.hexString
        }
        
        func application(
            _ application: UIApplication,
            didFailToRegisterForRemoteNotificationsWithError error: Error
        ) {
            print("Failed to register: \(error.localizedDescription)")
        }
        
        func application(
            _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
            fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
           
            print(userInfo)
            completionHandler(.newData)
        }
    }
    

    Extensions

    @available(iOS 10, *)
    extension AppDelegate : UNUserNotificationCenterDelegate {
        
        func userNotificationCenter(
            _ center: UNUserNotificationCenter,
            willPresent notification: UNNotification,
            withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            
            let userInfo = notification.request.content.userInfo
            print("Will Present User Info: \(userInfo)")
            
            completionHandler([[.banner, .sound]])
        }
        
        func userNotificationCenter(
            _ center: UNUserNotificationCenter,
            didReceive response: UNNotificationResponse,
            withCompletionHandler completionHandler: @escaping () -> Void) {
            
            let userInfo = response.notification.request.content.userInfo
            
            if response.actionIdentifier == "accept" {
                print("Did Receive User Info: \(userInfo)")
                
                completionHandler()
            }
        }
    }
    
    extension AppDelegate: MessagingDelegate {
        func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
            let dataDict: [String: String] = [AppDelegate.fcmToken: fcmToken ?? ""]
            NotificationCenter.default.post(name: NSNotification.Name("FCMToken"), object: nil, userInfo: dataDict)
    
            // Note: This callback is fired at each app startup and whenever a new token is generated.
            AppDelegate.fcmToken = fcmToken!
        }
    }
    
    extension Data {
        var hexString: String {
            let hexString = map { String(format: "%02.2hhx", $0) }.joined()
            return hexString
        }
    }