I want to transition my FireBase Auth class to the new (iOS17) @Observable pattern. The old pattern (ObservableObject/ EnviromentObject) works. I get this error message when using the pattern. I appreciate any tips.
Thread 1: "The default FirebaseApp instance must be configured before the default Authinstance can be initialized. One way to ensure this is to call FirebaseApp.configure() in the App Delegate's application(_:didFinishLaunchingWithOptions:) (or the @main struct's initializer in SwiftUI)."
old:
class Authentication : ObservableObject { ... }
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{
FirebaseApp.configure()
return true
}
}
@main
struct RApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@StateObject var authentication = Authentication()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(authentication)
.preferredColorScheme(.dark)
}
}
}
new:
@Observable
class Authentication { ... }
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool
{
FirebaseApp.configure()
return true
}
}
@main
struct RApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
@State private var authentication: Authentication = Authentication()
var body: some Scene {
WindowGroup {
RootView()
.environment(authentication)
.preferredColorScheme(.dark)
}
}
}
This is because @State
runs the initialiser = Authentication()
immediately, when the RApp
struct initialised. This happens before didFinishLaunchingWithOptions
. Presumably you are calling things like Auth.auth()
in the initialiser, which should be done after configure()
.
Compare this to @StateObject
, which runs the initialiser a bit later, by wrapping it in a @autoclosure
.
See also @Observable vs ObservableObject
If you want to use @Observable
, you can put the @State
in a View
instead of the RApp
struct. This makes the Authentication
initialiser run a bit later.
@main
struct RApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
Wrapper()
}
}
}
struct Wrapper: View {
@State var authentication = Authentication()
var body: some View {
RootView()
.environment(authentication)
.preferredColorScheme(.dark)
}
}
Alternatively, you can try restructuring your code so that you don't call Auth.auth()
(or anything that cannot be called before configure()
) in Authentication.init
.
For example, if you have
@Observable
class Authentication {
let auth = Auth.auth()
// ...
}
You can change this to
@Observable
class Authentication {
@ObservationIgnored
lazy var auth = Auth.auth()
// ...
}
and make sure you don't access auth
in init
.
In any case though, I think you should just keep using ObservableObject
. It's not deprecated or anything. There is no need to migrate to @Observable
if it just makes your life harder.