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.
2
Answers
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 theObservableObject
since there were issues with nested classes not updating the view with my@Published
variables.It’s true that you can get the Fireabsae user object when the authentication process is successful and then use it further in your code, but that doesn’t solve the entire problem, just half. So how about the sign-out of the user?
So if you want to track the user auth state, and take some action accordingly, then I recommend you use an auth state listener. In this way, you’ll be able to track the auth state across different components or across your entire application.