skip to Main Content

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


  1. Chosen as BEST ANSWER

    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.


  2. I’m looking for a way to get a user from my Firestore db AFTER authentication is completed.

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search