I’m new to swiftui development. Trying to build my first real app! I’m still tackling navigation and authentication where I’m trying to display the correct screens whether the authState is unauthenticated or authenticated. however, my app isn’t launching on the correct screen depending on the state it just crashes with no error message given.
I have a Router class with a single Routes enum for all my routes.
final class Router: ObservableObject {
public enum Routes: Hashable {
case landing
case emailAuth
case home
}
@Published var path: [Routes] = []
func navigate(to destination: Routes) {
path.append(destination)
}
func navigateBack() {
path.removeLast()
}
func navigateToRoot() {
path.removeLast(path.count)
}
}
this is my AuthViewModel where I’m using firebase addStateDidChangeListener to listen for state changes:
enum AuthState {
case unAuthenticated
case authenticated
}
@MainActor
class AuthViewModel: ObservableObject {
@AppStorage("email") var emailStore: String?
@Published var email = ""
@Published var errorMessage = ""
@Published var authState: AuthState = .unAuthenticated
@Published var user: User?
@Published var userEmail = ""
init() {
registerAuthStateHandler()
}
private var authStateHandle: AuthStateDidChangeListenerHandle?
func registerAuthStateHandler() {
if authStateHandle == nil {
authStateHandle = Auth.auth().addStateDidChangeListener{auth, user in
self.user = user
self.userEmail = user?.email ?? "(unknown)"
if (user != nil) {
self.authState = .authenticated
} else {
self.authState = .unAuthenticated
}
print("Curr authState: (self.authState)")
}
}
}
}
...
I think I’m correctly declaring my router with @StateObject within my view?:
struct ContentView: View {
@StateObject var authViewModel: AuthViewModel = AuthViewModel()
@StateObject var router: Router = Router()
var body: some View {
NavigationStack(path: $router.path) {
// Text("Loading...")
LandingScreen(path: $router.path)
.navigationDestination(for: Router.Routes.self) { route in
switch route {
case .landing:
let _ = print("landing")
LandingScreen(path: $router.path)
case .emailAuth:
AuthEmail(path: $router.path)
case .home:
Text("TODO: home page")
}
}
}
.onAppear {
print("Curr authState: (authViewModel.authState)")
if (authViewModel.authState == .authenticated) {
router.path = [.home]
} else {
router.path = [.landing]
}
print(router.path)
}
.environmentObject(router)
.environmentObject(authViewModel)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(AuthViewModel())
.environmentObject(Router())
}
}
within my subviews, I have something like:
struct LandingScreen: View {
@EnvironmentObject var authViewModel: AuthViewModel
@Binding var path: [Router.Routes]
var body: some View {
// ...
NavigationStack {
NavigationLink(value: Router.Routes.emailAuth) {
Text("Continue with email")
}
}
}
what I have tried was changing the initial screen in my NavigationStack to just a simple Text("Loading...")
however, my screen stays on the “loading…” screen and does not change the screen to the LandingScreen. despite my print logs saying that it has set the path correctly:
Curr authState: unAuthenticated
[app.Router.Routes.landing]
landing
I have also tried moving my onAppear code to an init() instead but it’s expected that init is called multiple times and still stays on the loading screen so that’s a no go…
I have also tried moving the init() up to the Router but I’m getting the same issue.
user25078877 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.