firebase-ios-sdk: Firebase Auth fails in SwiftUI App init

Description

PhoneAuthProvider.provider().verifyPhoneNumber.... will fail to execute. No success callback, no error either.

Reproducing the issue

On Xcode 14, While using the SwiftUI lifecyle, if you configure firebase in the @Main init() then Firebase auth wont work.

@Main 
struct AppName: App { 
   @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    init() {
       FirebaseApp.configure()
       // This init method runs before `didFinishLaunchingWithOptions` in Xcode 14
    }
  }

The reason I even configured Firebase in the init method is because configuring it in the AppDelegate's didFinishLaunchingWithOptions method would crash my app since the @Main init() method runs first and I had firebase methods running in there before didFinishLaunchingWithOptions even executed meaning I was running firebase database functions before FirebaseApp.configure() ran.

So Running FirebaseApp.configure() in the @Main init() method would result in PhoneAuthProvider.provider().verifyPhoneNumber not working at all. no callback, no error nothing. But Running it in didFinishLaunchingWithOptions would work providing I ran my firebase database methods after a publisher told me it was safe to do so.

My temporary fix.

@MainActor
class AppDelegateObserver: ObservableObject {
    @Published var didFinishLaunching = false
    static var shared: AppDelegateObserver { AppDelegateObserver() }
}

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, OSSubscriptionObserver {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        AppDelegateObserver.shared.didFinishLaunching = true
        return true
    }
    
    @Main 
    init() {
    ...
    AppDelegateObserver
            .shared
            .$didFinishLaunching
            .receive(on: RunLoop.main)
            .sink { [weak self] didFinishLaunching in
                /// Execute firebase database methods
            }
            .store(in: &disposables)

Ideally, I’d rather Configure Firebase in the @Main init() method than resort to this bandaid fix.

Hope that helps anyone who runs into this same issue. I tested with previous versions of Firebase eg. 9.4.1, 9.3.0 and the issue remained.

Firebase SDK Version

9.5.0

Xcode Version

14

Installation Method

Swift Package Manager

Firebase Product(s)

Analytics, Authentication, Crashlytics, DynamicLinks, Firestore, Functions

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!

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 27 (7 by maintainers)

Most upvoted comments

@forrestsyrett @onisiforos7 As I documented above, my solution was to have a trigger in didFinishLaunchingWithOptions that will tell me when the app has launched, then I can run my firebase methods in my main ViewModel’s init method. It’s not ideal but it works atleast.

@MainActor
class AppDelegateObserver: ObservableObject {
    @Published var didFinishLaunching = false
    static var shared: AppDelegateObserver { AppDelegateObserver() }
}

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, OSSubscriptionObserver {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        AppDelegateObserver.shared.didFinishLaunching = true
        return true
    }
}

  @Main 
  struct My_App: App {
    @StateObject var viewModel = ViewModel()
  }
  
  class ViewModel: ObservableObject {
      var disposables = Set<AnyCancellable>()
       
      init() {
            AppDelegateObserver
                    .shared
                    .$didFinishLaunching
                    .receive(on: RunLoop.main)
                    .sink { [weak self] didFinishLaunching in
                        /// Execute firebase database methods
                       // db.collection("Documents").document("id").getDocument() 
                    }
                    .store(in: &disposables)
                    }
        }
}

@otymartin Would you provide some context on why Authentication needs to be initialized before didFinishLaunching?

@otymartin Just spent a whole day fixing the same issue. Dead silence, no errors or warnings. It did indeed have to do with the placement of FirebaseApp.configure().

The architecture of the iOS sdk is problematic. Global instances everywhere make the whole setup precarious.

@rizafran I did call it in didFinishLaunching afterward however my problem is that since @Main init() runs before didFinishLaunching, it means any firebase operations I run in my @Main init() will crash since firebase has not been configured yet.

Authentication is one of those things I run as soon as the app launches.