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.
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.