Home > Software design >  Programatic navigation on authentication SwiftUI with Publishers
Programatic navigation on authentication SwiftUI with Publishers

Time:12-22

I have this code for authentication in a SwitUI application. SessionStore is an observable object which is injected into the main entry point of the app. When the sign is success then navigate to the dashboard. The isLogin variable changes but redirect does not happen. I don't understand what am not doing wrong.

// Main View

struct MyApp: App {
    @StateObject var session = SessionStore()
    
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        
        WindowGroup {
            SplashScreenView()
                .environmentObject(session)
        }
    }
}

// Splashscreen view which redirects to sign in or dashboard

struct SplashScreenView: View {
    
    @StateObject var session = SessionStore()
    
    var body: some View {
        Group {
            if session.isLogedIn {
                DashboardScreen()
            } else  {
                SignInScreen()
            }
        }
    }
}

// Session Store

class SessionStore: ObservableObject {
    @Published var session: User?
    @Published var profile: UserProfile?
    @Published var isLogedIn = false
    
    private var profileRepository = UserProfileRepository()
    
    func signUp(email: String, password: String, firstName: String, lastName: String, city: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
        Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
            if let error = error {
                print("Error signing up: \(error)")
                completion(nil, error)
                return
            }
            
            guard let user = result?.user else { return }
            print("User (user.uid) signed up.")
            
            let userProfile = UserProfile(uid: user.uid, firstName: firstName, lastName: lastName, city: city)
            self.profileRepository.createProfile(profile: userProfile) { (profile, error) in
                if let error = error {
                    print("Error while fetching the user profile: \(error)")
                    completion(nil, error)
                    return
                }
                self.profile = profile
                completion(profile, nil)
            }
        }
    }
    
    func signIn(email: String, password: String, completion: @escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
        Auth.auth().signIn(withEmail: email, password: password) { [self] (result, error) in
            if let error = error {
                print("Error signing in: \(error)")
                completion(nil, error)
                return
            }
            
            guard let user = result?.user else { return }
            print("User \(user.uid) signed in.")
            self.isLogedIn = true
            self.profileRepository.fetchProfile(userId: user.uid) { (profile, error) in
                if let error = error {
                    print("Error while fetching the user profile: \(error)")
                    completion(nil, error)
                    return
                }

                self.profile = profile
                completion(profile, nil)
            }
        }
    }
    
    func signOut() {
        do {
            try Auth.auth().signOut()
            self.session = nil
            self.profile = nil
        } catch let signOutError as NSError {
            print("Error signing out: \(signOutError)")
        }
    }
}

// In my view I call the method and update the log-in status or sign up. Which changes the state but does not redirect

struct SignInScreen: View {
    @ObservedObject var sessionStore = SessionStore()
var body: some View {
Button(action:signIn, label: {
                        Text("Sign In")
                    })
 }

    func signIn() {
        loading = true
        error = false
        sessionStore.signIn(email: signInViewModel.emailAddress, password: signInViewModel.password) { (profile, error) in
          if let error = error {
            print("Error when signing up: \(error)")
            return
          }
            sessionStore.isLogedIn = true
        }
    }
}

CodePudding user response:

I see you are using firebase. If you need to check authentication status it would be easy to do it this way.

Your main entry view

struct MyApp: App {
    @StateObject var session = SessionStore()
    
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        
        WindowGroup {
            SplashScreenView()
                .environmentObject(session)
        }
    }
}

In your session store add this code to check if the current is signed in using FirebaseAuth and not doing it manually.

class SessionStore: ObservableObject {
   
   // Previous code 
    var isSignIn: Bool {
        Auth.auth().currentUser?.uid != nil
    }

}

In your splash screen check for isSignIn.

struct SplashScreenView: View {
    
    @EnvironmentObject var session: SessionStore
    
    var body: some View {
        Group {
            if session.isSignIn {
                DashboardScreen()
            } else  {
                SignInScreen()
            }
        }
    }
}

CodePudding user response:

You have to use @EnvironmentObject var session: SessionStore instead of @StateObject in SplashScreenView.

CodePudding user response:

The goal is to have only one instance of your SessionStore(). Here, you redefined it twice: @StateObject var session = SessionStore()

In your MyApp, do

@StateObject var session = SessionStore()

and in others views, do

@EnvironmentObject var session: SessionStore

and, you forgot to pass your environmentObject to your SignInScreen

.environmentObject(session)
  • Related