Description
Description
A few days ago, Firebase was working like a charm, I had no issues with connecting to the server that I had connected to an iOS app. Then one day, I kept coming across this error
10.29.0 - [FirebaseMessaging][I-FCM002022] 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.
It was cryptic to a whole new level. So I began searching for an answer as to why this was happening. After doing some debugging, I came across something odd.
For context:
The Firebase server is running
Entitlement files are set to production (APS Environment and App Attest)
FirebaseAppDelegateProxyEnabled is disabled but has been enabled at times
I have AppCheck running well
I have added the APN Auth Key to the appropriate spot
I put the correct name in the Key ID and Team ID
Same for the Bundle ID and Team ID for the app
GoogleService.plist is in my app
I had no prior issue with the server until September 21 at around 12pm
No code in the AppDelegate was changed prior to September 21
Here's the portion of code it would run
//1. Called when the app is launched
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let providerFactory = DriverAppCheckProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
print("1st - didFinishLaunchingWithOptions")
// Firebase configuration
FirebaseApp.configure()
print("2nd - Firebase Configured")
print("3rd - UNUserNotificationCenter.current.delegate")
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
print("4th - authOptions")
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)
}
print("5th - application.registerForRemoteNotifications")
application.registerForRemoteNotifications()
print("6th - Messaging Delegate")
Messaging.messaging().delegate = self
print ("7th - True is returned")
return true
}
Then it runs some other method that is not in the AppDelegate and these print statements appeared
10.29.0 - [FirebaseInAppMessaging][I-IAM280002] Firebase In App Messaging was not configured with FirebaseAnalytics.
10.29.0 - [FirebaseMessaging][I-FCM002022] APNS device token not set before retrieving FCM Token for Sender ID '762004236193'.Be sure to re-retrieve the FCM token once the APNS device token is set.
10.29.0 - [FirebaseMessaging][I-FCM002022] Declining request for FCM Token since no APNS Token specified
10.29.0 - [FirebaseMessaging][I-FCM002010] The subscription operation failed due to an error getting the FCM token: Error Domain=com.google.fcm Code=505 "No APNS token specified before fetching FCM Token" UserInfo={NSLocalizedFailureReason=No APNS token specified before fetching FCM Token}.
Afterwards (with swizzling on), it runs these methods
// 2. Called when the app successfully registers with APNS and receives a device token
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("8th - Setting APNs Token")
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.unknown)
print("9th - Assigning APNs Token to deviceToken")
// Set the APNS token for Firebase Messaging
Messaging.messaging().apnsToken = deviceToken
print("Device Token received: \(deviceToken)")
// Fetch the FCM token now that APNS token is available
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM token: \(error)")
} else if let token = token {
print("FCM token: \(token)")
// You can also subscribe to topics here now that FCM token is available
Messaging.messaging().subscribe(toTopic: "newPickupRequest") { error in
if let error = error {
print("Error subscribing to topic: \(error)")
} else {
print("Subscribed to newPickupRequest topic")
}
}
}
}
}
Here's the print statements
Error subscribing to topic: The operation couldn’t be completed. No APNS token specified before fetching FCM Token
8th - Setting APNs Token
9th - Assigning APNs Token to deviceToken
Device Token received: 32 bytes
FCM token received: [The FCM token]
FCM token: [The FCM token]
Subscribed to newPickupRequest topic
Based on the bebugging I made, it seems like some other method is being called in between both methods.
What could be the issue here? Is it a bug or glitch I'm not aware of?
In case needed, here the full class and the [NameofApp]App
import UIKit
import UserNotifications
import Firebase
import FirebaseCore
import FirebaseMessaging
import TabularData
class DriverAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
return AppAttestProvider(app: app)
}
}
class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate {
let gcmMessageIDKey = "gcm.message_id"
// 1. Called when the app is launched
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let providerFactory = DriverAppCheckProviderFactory()
AppCheck.setAppCheckProviderFactory(providerFactory)
print("1st - didFinishLaunchingWithOptions")
// Firebase configuration
FirebaseApp.configure()
print("2nd - Firebase Configured")
print("3rd - UNUserNotificationCenter.current.delegate")
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
print("4th - authOptions")
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)
}
print("5th - application.registerForRemoteNotifications")
application.registerForRemoteNotifications()
print("6th - Messaging Delegate")
Messaging.messaging().delegate = self
print ("7th - True is returned")
return true
}
// 2. Called when the app successfully registers with APNS and receives a device token
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("8th - Setting APNs Token")
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.unknown)
print("9th - Assigning APNs Token to deviceToken")
// Set the APNS token for Firebase Messaging
Messaging.messaging().apnsToken = deviceToken
print("Device Token received: \(deviceToken)")
// Fetch the FCM token now that APNS token is available
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM token: \(error)")
} else if let token = token {
print("FCM token: \(token)")
// You can also subscribe to topics here now that FCM token is available
Messaging.messaging().subscribe(toTopic: "newPickupRequest") { error in
if let error = error {
print("Error subscribing to topic: \(error)")
} else {
print("Subscribed to newPickupRequest topic")
}
}
}
}
}
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)
}
// 3. Called when the app fails to register for remote notifications
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for remote notifications: \(error)")
}
// 4. Called when a new FCM token is generated
internal func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("FCM token received: \(fcmToken ?? "No FCM token")")
// Optionally, handle additional operations like topic subscription here if needed
}
func scheduleDailyReport() {
print("Scheduling the daily report...")
// Schedule the generation of the report
var dateComponents = DateComponents()
dateComponents.hour = 16 // Adjust to your preferred time
dateComponents.minute = 10
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)
}
}
let csvURL = self.createCSVReport(from: services)
self.displayCSVFile(at: csvURL)
historyRef.removeValue { error, _ in
if let error = error {
print("Error deleting history: \(error.localizedDescription)")
} else {
print("History deleted successfully")
}
}
}
}
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)
}
}
private func handleDailyReportTrigger() {
let viewModel = AdminDashboardViewModel() // Replace with your actual way of accessing the viewModel
viewModel.generateDailyReport()
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
let userInfo = notification.request.content.userInfo
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
// ...
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
return [[.alert, .sound]]
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
let userInfo = response.notification.request.content.userInfo
// ...
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
-> UIBackgroundFetchResult {
// 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)
return UIBackgroundFetchResult.newData
}
}
@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)
// 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])
}
}
Here's the DriverLocationApp
import SwiftUI
import FirebaseCore
@main
struct DriverLocationApp: App {
// register app delegate for Firebase setup
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
private var Notedelegate: NotificationDelegate = NotificationDelegate()
init() {
let center = UNUserNotificationCenter.current()
center.delegate = appDelegate
center.requestAuthorization(options: [.alert, .sound, .badge]) { result, error in
if let error = error {
print("Hello There, I'm from the DriverLocationApp")
print(error)
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Reproducing the issue
No response
Firebase SDK Version
11.2
Xcode Version
16.1
Installation Method
Swift Package Manager
Firebase Product(s)
App Check, Database, In-App Messaging, Messaging
Targeted Platforms
iOS
Relevant Log Output
No response
If using Swift Package Manager, the project's Package.resolved
Expand Package.resolved
snippet
Replace this line with the contents of your Package.resolved.
If using CocoaPods, the project's Podfile.lock
Expand Podfile.lock
snippet
Replace this line with the contents of your Podfile.lock!