Implementing navigation based on auth changes using flutter_bloc and go_router.
What i need to do is at start if User is authenticated it should be redirected to HomeScreen with the ability to navigate through the auth flow...(HomeScreen,TestScreen1,TestScreen2)
If User is unAunthenticated at any point it should be redirected to LoginScreen and if Unknown then to Splash Screen.
I have gone through many stackoverflow questions and github issues but none of them provides a best pratice of how could we acheive this.
Problems:
- Cannot navigate it in the auth flow, HomeScreen -> Test1 || HomeScreen -> Test2
Gives the following assetion:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<$T>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
The context used was: $context
- When working with flutter_web, if I'm on Home Screen (/auth/home_screen) and change the URL manually to /login_screen it throws an exception:
======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for GoRouteInformationProvider:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
Changing to context.read
instead of watch
is not what i'm really into, because then i cannot listen to changes from the bloc.
What i have tried soo far is:
My AppRouter:
class AppRouter {
GoRouter get router => _goRouter;
late final GoRouter _goRouter = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
name: 'SPLASH',
builder: (context, state) => const SplashScreen(),
),
GoRoute(
path: '/auth',
name: 'AUTH',
builder: (context, state) => HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'home_screen',
name: 'HOME',
builder: (context, state) => HomeScreen(),
),
GoRoute(
path: 'test_screen_1',
name: 'TEST_SCREEN_1',
builder: (context, state) => TESTSCREEN1(),
),
GoRoute(
path: 'test_screen_2',
name: 'TEST_SCREEN_2',
builder: (context, state) => TESTSCREEN2(),
),
],
),
GoRoute(
path: '/login_screen',
name: 'LOGIN',
builder: (context, state) => LoginScreen(),
),
],
redirect: (context, state) {
final authState = context.watch<AuthBloc>().state;
final loginLocation = state.namedLocation('LOGIN');
final authFlow = state.namedLocation('AUTH');
final splashLocation = state.namedLocation('SPLASH');
if (authState is AuthStateAuthenticated) {
return authFlow;
} else if (authState is AuthStateUnAuthenticated) {
return loginLocation;
} else {
return splashLocation;
}
},
);
}
App:
class App extends StatelessWidget {
AppRouter appRouter = AppRouter();
@override
Widget build(BuildContext context) {
return RepositoryProvider<AuthService>(
create: (context) => AuthService(),
child: MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(create: (context) => AuthBloc(authService: RepositoryProvider.of<AuthService>(context))),
],
child: MaterialApp.router(
routerDelegate: appRouter.router.routerDelegate,
routeInformationParser: appRouter.router.routeInformationParser,
routeInformationProvider: appRouter.router.routeInformationProvider,
),
),
);
}
}
listen:false
when getting the auth state. So, instead ofProvider.of<Auth>(context)
I needed to callProvider.of<Auth>(context, listen: false)
. In your case, it seems due to this:context.watch<AuthBloc>().state
– Komsomol