1

How can i make routes available depending on auth state in flutter app using go_router.

I have logged_in, logged_out and need_verification auth states in my flutter app and i have a few routes that i want to be accessible to a user depending on their auth state. I am using riverpod to manage the state and go_router for the routing.

I implemented a logic to redirect to either HomeScreen, LogInScreen or VerifyEmailScreen depending on the current auth state, it works alright. The problem is i also have a SignUpScreen and ForgotPasswordScreen which should be available in the logged_out state, but when i use context.go() to navigate to either of them, the redirect logic kicks in and keeps me from going to the new screen because the state is unchanged.

How do i make all logged out routes available in the logged_out state?

Reazy_ai
  • 26
  • 3

2 Answers2

1

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.

0

You could add some if's to your redirection code that will not redirect when you are on SignUpScreen or ForgotPasswordScreen. Something like that:

redirect(...) {
  String currentPath;
  if (state == HomeScreen.state && 
     (currentPath != SignUpScreen.path ||
      currentPath != ForgotPasswordScreen.path)) {
    return HomeScreen.path;
  }
  ... and others
}

Or else:

redirect(...) {
  String currentPath;
  if (currentPath == SignUpScreen.path ||
      currentPath == ForgotPasswordScreen.path) {
    return null; // do not redirect
  }
  ... and others
}
Ruble
  • 2,589
  • 3
  • 6
  • 29