Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.
    }
}
like image 832
M1X Avatar asked Dec 25 '20 00:12

M1X


People also ask

Can I send push notifications without FCM?

How it is possible? It's definitely possible -- you don't have to use Firebase to deliver push notifications.

Can you send push notifications with Firebase?

Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices that allows you to deliver and receive messages and notifications on iOS, Android, and the web at no cost.

Why you use FCM notification instead of APNs?

APNs and WNS do not support multiple platforms. They are designed to work with their native platforms. But, FCM supports multiple platforms such as Android and iOS and even supports Chrome web apps.


2 Answers

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
    }
}
like image 90
David Avatar answered Nov 15 '22 06:11

David


Le_fretApp.init is too early to work with UIApplication.shared, because it is not initialised there yet.

Try to do that on scene creation (or in other place where you find desirable and app already initialized).

@main
struct Le_fretApp: App {
  // ... other code here

    func createScene() -> some Scene {
        if nil == UIApplication.shared.delegate {
            UIApplication.shared.delegate = NotificationsService.Shared  // << !!
        }

        return WindowGroup {
            // ... your scene content here
        }
    }

    var body: some Scene {
        createScene()
    }

and, btw, I assume this property also should refer to same instance of service, i.e. shared

var notificationsService = NotificationsService.Shared    // !!
like image 41
Asperi Avatar answered Nov 15 '22 05:11

Asperi