I ran into a similar problem and thought I would share what I reached.
The app I'm building currently has 3 states: newUser, signedUpButNotVerified and verified user. This is very similar to your setup.
I started by creating a function that would return me the status of the current user
enum UserStatus { notSignedUp, needsEmailVerification, verified }
class Authenticator {
UserStatus getUserStatus() {
if (_auth.currentUser == null) {
debugPrint('no user');
return UserStatus.notSignedUp;
}
if (_auth.currentUser!.emailVerified) {
debugPrint('email verified');
return UserStatus.verified;
} else {
debugPrint('email not verified');
return UserStatus.needsEmailVerification;
}
}
}
I created the following 3 providers:
final authenticationProvider = Provider<Authenticator>((ref) {
return Authenticator();
});
final authStateProvider = StreamProvider<User?>((ref) {
return ref.read(authenticationProvider).authStateChange;
});
final userAuthStatusProvider = Provider.autoDispose<UserStatus>((ref) {
return ref.read(authenticationProvider).getUserStatus();
});
I implemented the paths you were asking about as a Set like this:
final Set<String> screensAllowedWhenNoAuth = {Screens.welcome, Screens.login, Screens.signup};
final Set<String> screensAllowedWhenSignedUpAndNotVerified = {
Screens.emailConfirmationScreen,
Screens.signup,
Screens.login
};
Now, change your GoRouter to a provider to be able to use ref inside of it:
final routerProvider = Provider<GoRouter>((ref) {
return GoRouter(
navigatorKey: _rootKey,
debugLogDiagnostics: false,
initialLocation: Screens.welcome,
routes: [
// Your routes
],
redirect: (context, state) {
final authState = ref.read(authStateProvider);
debugPrint('redirecting to ${state.location}');
if (authState.isLoading || authState.hasError) return null;
final userAuthState = ref.read(userAuthStatusProvider);
debugPrint('userAuthState: $userAuthState');
switch (userAuthState) {
case UserStatus.notSignedUp:
if (screensAllowedWhenNoAuth.contains(state.location)) {
debugPrint('Not signed up but allowed to access ${state.location}');
return null;
} else {
debugPrint('Trying to access ${state.location} when not signed up');
return Screens.welcome;
}
case UserStatus.needsEmailVerification:
if (screensAllowedWhenSignedUpAndNotVerified.contains(state.location)) {
debugPrint('Needs email verification but allowed to access ${state.location}');
return null;
} else {
debugPrint('Trying to access ${state.location} when not verified');
return Screens.emailConfirmationScreen;
}
case UserStatus.verified:
debugPrint('user is verified');
return null;
}
},
);
});
You can simply add the screens you want to be available to the user according to their current state in its Set.
I'm not sure if this is the best implementation but it's the best one I could come up with so far.
You can checkout this discussion and this repo for more.