I'm looking for a way to get a user from my Firestore db AFTER authentication is completed and I get the UID for the user but BEFORE my view loads on SwiftUI/Swift 5+. Or alternatively, I can have a ProgressView in between.
In my ContentView view, I have a conditional clause on whether a user is onboarded or not. This is a field in my User document on Firebase that I need to access.
The problem I'm running into is that when my view loads, it loads with the default values of my User struct when I need the values from the database.
Here's my solution right now:
I made a SessionStore class to handle the logins with Firebase:
class SessionStore : ObservableObject {
u/Published var session: SessionUser? { didset { self.didChange.send(self) } }
var handle: AuthStateDidChangeListenerHandle?
// Auth listening by running addStateDidChangeListener
func listen() { ... }
// Unbind the Auth listener
func unbind() { ... }
func handleGoogleLogin() {
GIDSignIn.sharedInstance.signIn(withPresenting: getRootViewController()) {
signInResult, err in
...
signInResult.user.refreshTokensIfNeeded { user, error in
...
Auth.auth().signIn(with: credential) { result, err in
print("success!")
// TODO: run fetch user here somehow
}
}
}
}
}
Then in my SessionUser object, I have the call to fetch data from the collection on Firestore:
class SessionUser : ObservableObject {
@Published var user: User?
@Published var isUserOnboarded: Bool
var uid: String
init(uid) { ... }
func fetchSessionUser() async {
do {
// Tries to get user from the datastore first in case user exists already
if doesUserExist() {
// Get user from Firestore db
let doc = try await db.collection("users").document("\(userId)")
if doc.exists {
self.user = try document.data(as: User.self)
self.isUserOnboarded = self.user.isUserOnboarded
}
} else {
// Create user
}
}
}
}
For posterity, here's the ContentView view:
struct ContentView: View {
@StateObject var session: SessionStore = SessionStore()
var body: some View {
VStack {
if session.session != nil || Auth.auth().currentUser != nil {
if session.session?.isUserOnboarded {
MainView()
} else {
OnboardingView()
}
} else {
StartPageView()
}
}
}
I've tried a lot of things such as escaping the object in my fetch call, creating an encapsulated class with a published variable for the isUserOnboarded (as shown above), and playing around with DispatchQueue.main.async calls, but nothing has worked so far.
Previous answers on StackOverflow/online show the Auth procedure for Firebase as being async (but it's not anymore I guess) and it doesn't allow for me to call it with an await.
I ended up switching to a snapshot listener to avoid the await call and then also changed my SessionUser to have an @Observable
macro instead of inheriting the ObservableObject
since there were issues with nested classes not updating the view with my @Published
variables.