firebase / quickstart-ios

Firebase Quickstart Samples for iOS
https://firebase.google.com
Apache License 2.0
2.79k stars 1.47k forks source link

How can I retrieve the APNs before the FCM token in Firebase in Xcode specifically? #1620

Open Emanuel-111 opened 2 weeks ago

Emanuel-111 commented 2 weeks ago

Before encountering the error, I was using Xcode which had Firebase installed. At around 12pm today, I began encountering an error that looked like this

APNS device token not set before retrieving FCM Token for Sender ID 'XXXXXXXXX'.Be sure to re-retrieve the FCM token once the APNS device token is set.

Prior to 12pm, I did not encounter the error at all

After tinkering with it for 9 hours, I tried moving methods around, looked up many websites to find the exact error, debugging, and even went to YouTube of all places, but can't figure out where the error is coming from.

If it helps, I am currently using Xcode 16 with Firebase version 10.27.

Here's the code for anyone who thinks they can find the answer

This is in my AppDelegate from constant debugging

For extra context:

I have the app running on my iPhone 15 Pro Max and was running well before the error I have Background Modes enabled (Background fetch, processing, remote notifications) In my Firebase Console, I have the APN key in my Cloud Messaging section I have added the app to my Firebase server I have the Google Info.plist in my code I have the app registred for App Attest (AppCheck) and DeviceCheck

`import UIKit import UserNotifications import Firebase import FirebaseMessaging import TabularData import FirebaseInAppMessaging

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

var window: UIWindow?

// Configure Firebase when the app launches
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure()

    print("1st launch")
    // Set up push notification permissions
    registerForPushNotifications(application)

    // Set the messaging delegate
    print("2nd launch")
    Messaging.messaging().delegate = self

    return true
}

// Register for push notifications
func registerForPushNotifications(_ application: UIApplication) {
    print("3rd launch")
    UNUserNotificationCenter.current().delegate = self
    let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
        print("Permission granted: \(granted)")
    }
    application.registerForRemoteNotifications()
}

// Called when APNs has assigned a device token to the app
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

    // Forward the APNs token to Firebase
    Messaging.messaging().apnsToken = deviceToken
    print("APNs Token registered: \(deviceToken)")
}

// Handle any errors when trying to register for remote notifications
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("Failed to register for remote notifications: \(error)")
}

// Called when Firebase messaging gets a new FCM token
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    guard let fcmToken = fcmToken else { return }
    print("Firebase FCM token: \(fcmToken)")

    // Optionally, send this token to your server
    // sendFCMTokenToServer(fcmToken)
}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    handleDailyReportTrigger()
    completionHandler([.banner, .sound, .badge])
}

func scheduleDailyReport() {
    print("Scheduling the daily report in scheduleDailyReport...")

    // Schedule the generation of the report
    var dateComponents = DateComponents()
    dateComponents.hour = 16  // Adjust to your preferred time
    dateComponents.minute = 10

    print("At the UserNotificationCenter")

    let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
    let request = UNNotificationRequest(identifier: "DailyReport", content: UNMutableNotificationContent(), trigger: trigger)

    UNUserNotificationCenter.current().add(request) { error in
        if let error = error {
            print("Error scheduling daily report: \(error.localizedDescription)")
        } else {
            print("Daily report scheduled successfully.")
        }
    }

    // Automatically generate and display the report when the scheduled time is reached
    generateDailyReport()
}

private func generateDailyReport() {
    let startOfDay = Calendar.current.startOfDay(for: Date())
    let endOfDay = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay)!
    let historyRef = Database.database().reference().child("history")

    let query = historyRef.queryOrdered(byChild: "timestamp")
        .queryStarting(atValue: startOfDay.timeIntervalSince1970)
        .queryEnding(atValue: endOfDay.timeIntervalSince1970)

    query.observeSingleEvent(of: .value) { snapshot in
        var services: [[String: Any]] = []
        for child in snapshot.children {
            if let childSnapshot = child as? DataSnapshot, let serviceData = childSnapshot.value as? [String: Any] {
                services.append(serviceData)
            }
        }
        // Only proceed with CSV if we have data
        if !services.isEmpty {
            let csvURL = self.createCSVReport(from: services)
            self.displayCSVFile(at: csvURL)

            // Optionally delete the history
            historyRef.removeValue { error, _ in
                if let error = error {
                    print("Error deleting history: \(error.localizedDescription)")
                } else {
                    print("History deleted successfully")
                }
            }
        } else {
            print("No services found for the day.")
        }
    } withCancel: { error in
        print("Error fetching history data: \(error.localizedDescription)")
    }
}

private func createCSVReport(from services: [[String: Any]]) -> URL {
    var dataFrame = DataFrame()

    // Create columns
    let customerNames = Column(name: "Customer Name", contents: services.map { $0["customerName"] as? String ?? "N/A" })
    let addresses = Column(name: "Address", contents: services.map { $0["address"] as? String ?? "N/A" })
    let phoneNumbers = Column(name: "Phone Number", contents: services.map { $0["phoneNumber"] as? String ?? "N/A" })
    let driverNames = Column(name: "Driver Name", contents: services.map { $0["driverName"] as? String ?? "N/A" })
    let statuses = Column(name: "Status", contents: services.map { $0["status"] as? String ?? "N/A" })

    let timestamps = Column(name: "Timestamp", contents: services.map { service in
        let timestamp = service["timestamp"] as? TimeInterval ?? 0
        let date = Date(timeIntervalSince1970: timestamp)
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return dateFormatter.string(from: date)
    })

    // Add columns to the DataFrame
    dataFrame.append(column: customerNames)
    dataFrame.append(column: addresses)
    dataFrame.append(column: phoneNumbers)
    dataFrame.append(column: driverNames)
    dataFrame.append(column: statuses)
    dataFrame.append(column: timestamps)

    // Export DataFrame to CSV format
    let csvData = try! dataFrame.csvRepresentation()

    // Save to directory
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let fileURL = documentsDirectory.appendingPathComponent("Service_Report_\(Date().toFormattedString()).csv")

    try! csvData.write(to: fileURL)
    print("CSV file created at: \(fileURL.path)")
    return fileURL
}

private func displayCSVFile(at url: URL) {
    let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)

    if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        windowScene.windows.first?.rootViewController?.present(activityViewController, animated: true, completion: nil)
    }
}

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    print("UserInfo: \(userInfo)")
    handleDailyReportTrigger()
    completionHandler()
}

private func handleDailyReportTrigger() {
    let viewModel = AdminDashboardViewModel() // Access your view model here
    viewModel.generateDailyReport()
}

}

@MainActor class NotificationManager: ObservableObject{ @Published private(set) var hasPermission = false static let shared = NotificationManager()

init() {
    Task{
        await getAuthStatus()
    }
}

func request() async{
    do {
        try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound])
         await getAuthStatus()
    } catch{
        print(error)
    }
}

func getAuthStatus() async {
    let status = await UNUserNotificationCenter.current().notificationSettings()
    switch status.authorizationStatus {
    case .authorized, .ephemeral, .provisional:
        hasPermission = true
    default:
        hasPermission = false
    }
}

func postNewPickupRequestNotification() {
       NotificationCenter.default.post(name: .newPickupRequest, object: nil)
   }

@MainActor
func observeNewPickupRequestNotification(observer: Any, selector: Selector) {
       NotificationCenter.default.addObserver(observer, selector: selector, name: .newPickupRequest, object: nil)
   }

}

extension Notification.Name { static let newPickupRequest = Notification.Name("newPickupRequest") }

@MainActor class NotificationHandler: NSObject, ObservableObject { @Published var newRequestAlert = false

override init() {
    super.init()
    Task { @MainActor in
        NotificationManager.shared.observeNewPickupRequestNotification(observer: self, selector: #selector(handleNewPickupRequest))
    }
}

@objc private func handleNewPickupRequest() {
    newRequestAlert = true
    scheduleLocalNotification(title: "New Pickup Request", body: "A new pickup request has been added.", timeInterval: 1)

    print("handleNewPickupRequest is here!")
    // Dismiss alert after some time (e.g., 3 seconds)
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        self.newRequestAlert = false
    }
}

private func scheduleLocalNotification(title: String, body: String, timeInterval: TimeInterval) {
    let content = UNMutableNotificationContent()
    content.title = title
    content.body = body
    content.sound = .default

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
    let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

    UNUserNotificationCenter.current().add(request)
}

}

class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    let userInfo = response.notification.request.content.userInfo
    print(userInfo)

    completionHandler()

}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    completionHandler([.banner, .sound, .badge])
}

} `