Search code examples
flutteraws-amplifyriverpodflutter-go-router

Flutter GoRouter Redirection with Riverpod and Amplify Stream


In my project, I'm using GoRouter for navigation, Riverpod for state management and an AWS backend for users and data. I have a StreamProvider set up with DynamoDB data from a user like so:

Stream<AuthorizationData> authState(AuthStateRef ref) async* {
  final queryRequest = ModelQueries.list(
    AuthorizationData.classType,
  );
  final queryResponse = await Amplify.API.query(request: queryRequest).response;
  AuthorizationData record;
  if (queryResponse.data?.items.isEmpty ?? true) {
    //create new data if none exists
    );
    final mutationRequest = ModelMutations.create(newData);
    final mutationResponse =
        await Amplify.API.mutate(request: mutationRequest).response;
    if (mutationResponse.data == null) {
      throw Exception('Failed to create initial AuthorizationData');
    }
    record = mutationResponse.data!;
  } else {
    record = queryResponse.data!.items.first!;
  }

  yield record;

  final subscriptionRequest =
      ModelSubscriptions.onUpdate(AuthorizationData.classType);
  final subscriptionStream = Amplify.API.subscribe(
    subscriptionRequest,
    onEstablished: () =>
        safePrint('AuthorizationData subscription established'),
  );

My GoRouter redirection never happens. Here is a simplified version:

GoRouter router(RouterRef ref) {
  return GoRouter(
      debugLogDiagnostics: true,
      initialLocation: '/',
      routes: [
        // Splash screen route
        GoRoute(
          path: '/',
          builder: (context, state) => const AuthenticatedView(
            child: SplashScreen(),
          ),
        ),
        GoRoute(
          path: '/error',
          builder: (context, state) => const ErrorPage(),
        ),
        // Welcome route
        GoRoute(
          path: '/home',
          builder: (context, state) => SignedInPage()),
        // Main app with tabs
        GoRoute(
          path: '/account',
          builder: (context, state) => ParentAccountPage())
      ],
      redirect: (context, state) {
        final auth = ref.read(authStateProvider);
        return auth.when(
          data: (data) {
            // Compute a boolean based on your fields
            final doneOneStep = data.hasDoneFirstStep == true ||
                data.hasDoneSecondStep == true ||
                data.hasDoneThirdStep == true;
            final isSplash = state.uri.path == '/';
            if (isSplash) return doneOneStep ? '/home' : '/welcome';
            // When not on the splash, allow access only if doneOneStep is true
            return doneOneStep ? null : '/';
          },
          loading: () => '/',
          error: (_, __) => '/error',
        );
      });
}

And here is how I'm first calling the router:

class _MyAppState extends ConsumerState<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Authenticator(
      authenticatorBuilder: (BuildContext context, AuthenticatorState state) {
        return LoginScaffold(
          step: state.currentStep,
          toSignUp: () => state.changeStep(AuthenticatorStep.signUp),
          toSignIn: () => state.changeStep(AuthenticatorStep.signIn),
        );
      },
      child: MaterialApp.router(
        debugShowCheckedModeBanner: false,
        routerConfig: ref.read(routerProvider),
        builder: Authenticator.builder(),
      ),
    );
  }

For some reason, I cannot get the redirect to ever fire.

Is there something I've missed?

After debugging, I found that the AsyncValue never changes from AsyncLoading(). But when I take the redirect off, stop watching/reading the ref, and just manually navigate to the account page (where I am also watching authStateProvider, I get the value loading and also when the data yields.

I've even debugged the yield line in the StreamProvider and it does yield the data from the user. But I can't get the value to change in the GoRouter.

I have also tried changing the ref to ref.listen() and adding it to the refreshListenable to the GoRouter but that also didn't result in a redirect.


Solution

  • refreshListenable is the wrong approach here, because your provider is a ProviderListenable, which is not a Listenable.

    The easiest way to get this to work is to trigger a reroute when the provider changes by adding the following to the top of your router provider:

    ref.listen(authStateProvider, (_, __) => state.refresh());
    

    This will trigger state (in this case, a GoRouter instance) to execute refresh(), which will send you through the redirect callback properly. If you want only certain state changes to trigger, feel free to add a select in there.