GoRouter嵌套导航下,无法用根MultiBlocProvider,如何使用单一BlocProvider?
我使用GoRouter实现嵌套导航时,希望让嵌套组件共享同一个RentCarBloc,但无法在根组件中使用MultiBlocProvider——原因是用户登出账号时,Bloc中的数据会被保留。目前我在RentCarsScreen中创建了BlocProvider,但嵌套的RentCarDescScreen等组件无法共享该Bloc,请问该如何实现?
相关代码
Router
StatefulShellBranch( navigatorKey: _rentShellNavigatorKey, routes: <RouteBase>[ GoRoute( path: AppRouter.rentCarList.routePath, pageBuilder: (BuildContext context, GoRouterState state) => const NoTransitionPage<dynamic>(child: RentCarsScreen()), routes: <RouteBase>[ GoRoute( path: "${AppRouter.rentCarDesc.routeName}/:id", name: AppRouter.rentCarDesc.routeName, builder: (BuildContext context, GoRouterState state) => RentCarDescScreen(id: state.pathParameters["id"]!), routes: <RouteBase>[ GoRoute( path: AppRouter.carReservation.routeName, name: AppRouter.carReservation.routeName, builder: (BuildContext context, GoRouterState state) => CarReservationScreen(id: state.pathParameters["id"]!), ), ] ), ]) ]),
RentCarsScreen
@override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF0F2F4), body: BlocProvider<RentCarBloc>( create: (BuildContext context) => locator<RentCarBloc>()..add(GetRentCarListEvent()), child: BlocBuilder<RentCarBloc, RentCarState>( builder: (BuildContext context, RentCarState state) { // ui code
RentCarDescScreen
Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF0F2F4), body: BlocBuilder<RentCarBloc, RentCarState>( builder: (BuildContext context, RentCarState state) { // ui code
要实现嵌套组件共享同一个Bloc,核心是将BlocProvider提升到嵌套导航的根路由层级,而非放在RentCarsScreen的内部,具体步骤如下:
在Router的pageBuilder中包裹BlocProvider
修改根路由的pageBuilder,将BlocProvider直接包裹在RentCarsScreen外层,这样整个嵌套路由分支下的所有页面都能获取到同一个Bloc实例:StatefulShellBranch( navigatorKey: _rentShellNavigatorKey, routes: <RouteBase>[ GoRoute( path: AppRouter.rentCarList.routePath, pageBuilder: (BuildContext context, GoRouterState state) => NoTransitionPage<dynamic>( // 在这里注入BlocProvider,覆盖整个嵌套路由分支 child: BlocProvider<RentCarBloc>( create: (BuildContext context) => locator<RentCarBloc>()..add(GetRentCarListEvent()), child: const RentCarsScreen(), ), ), routes: <RouteBase>[ GoRoute( path: "${AppRouter.rentCarDesc.routeName}/:id", name: AppRouter.rentCarDesc.routeName, builder: (BuildContext context, GoRouterState state) => RentCarDescScreen(id: state.pathParameters["id"]!), routes: <RouteBase>[ GoRoute( path: AppRouter.carReservation.routeName, name: AppRouter.carReservation.routeName, builder: (BuildContext context, GoRouterState state) => CarReservationScreen(id: state.pathParameters["id"]!), ), ] ), ]) ]),移除RentCarsScreen内部的BlocProvider
现在Bloc已经在路由层级提供,RentCarsScreen只需保留BlocBuilder即可:@override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF0F2F4), body: BlocBuilder<RentCarBloc, RentCarState>( builder: (BuildContext context, RentCarState state) { // ui code }, ), ); }登出时自动清理Bloc数据
由于Bloc绑定在嵌套路由分支上,当用户登出跳转到其他页面时,GoRouter会销毁该分支的导航栈,BlocProvider会自动销毁对应的Bloc实例。如果需要主动重置状态,可在登出时发送重置事件:void handleLogout(BuildContext context) { // 发送重置状态事件 context.read<RentCarBloc>().add(ResetRentCarStateEvent()); // 跳转到登录页 context.go(AppRouter.login.routePath); }
完成以上修改后,RentCarDescScreen和CarReservationScreen就能通过context.read<RentCarBloc>()或BlocBuilder正常共享同一个Bloc实例,同时登出时Bloc会随导航栈销毁,不会残留数据。
内容的提问来源于stack exchange,提问作者Murlodin

