在go_router v5.0的redirect方法中无法访问注入的BLoC
问题:Flutter Bloc + GoRouter 多表单页面的路由重定向问题
使用flutter_bloc和go_router实现两页多步骤表单,通过ShellRoute为两个路由共享同一个MultiFormBloc实例。但在step2路由的redirect方法中,无法从BuildContext获取到这个Bloc,导致无法根据step1的完成状态判断是否重定向回step1(防止用户直接输入/step2访问)。
用户的路由配置代码:
... ShellRoute( builder: (context, state, child) { // 通过ShellRoute为两个路由提供同一个MultiFormBloc实例 return BlocProvider( create: (context) => MultiFormBloc(), child: child, ); }, routes: [ GoRoute( name: 'step1', path: 'step1', builder: (context, state) => Step1(), routes: [ GoRoute( name: 'step2', path: 'step2', builder: (context, state) => Step2(), redirect: (context, state) { // 报错:无法获取MultiFormBloc,context中未注入 if (context.read<MultiFormBloc>().state.step1Value == null) { return '/step1'; } return null; }, ), ], ), ], ), ...
解决方案
原因说明
redirect方法的执行时机早于ShellRoute的builder构建,此时ShellRoute中提供的Bloc还未被注入到context中,所以无法直接读取。
方法1:将Bloc提升到全局层级提供
如果这个MultiFormBloc仅用于该多步骤表单流程,可将其提供在GoRouter的父级(比如MaterialApp的上层),这样整个路由树的context都能访问到:
// 在main.dart或路由配置的上层 BlocProvider( create: (context) => MultiFormBloc(), child: MaterialApp.router( routerConfig: _router, ), )
之后在step2的redirect方法中就能正常使用context.read<MultiFormBloc>()判断状态。
方法2:在Step2页面内部处理重定向
放弃在路由配置的redirect中处理,改为进入Step2页面后检查状态:
class Step2 extends StatefulWidget { @override State<Step2> createState() => _Step2State(); } class _Step2State extends State<Step2> { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final bloc = context.read<MultiFormBloc>(); if (bloc.state.step1Value == null) { context.goNamed('step1'); } }); } @override Widget build(BuildContext context) { // Step2页面内容 return Scaffold(body: ...); } }
这种方式更符合ShellRoute的Bloc作用域,因为进入Step2页面时,ShellRoute的builder已经完成,context中已经注入了MultiFormBloc。
方法3:使用GoRouter的refreshListenable监听Bloc状态
通过Bloc的stream创建Listenable,让GoRouter能感知状态变化并触发重定向:
// 创建一个监听Bloc状态的Listenable class MultiFormListenable extends ChangeNotifier { MultiFormListenable(this.bloc) { _subscription = bloc.stream.listen((_) => notifyListeners()); } final MultiFormBloc bloc; late final StreamSubscription _subscription; @override void dispose() { _subscription.cancel(); super.dispose(); } } // 在路由配置中使用 final _bloc = MultiFormBloc(); final _router = GoRouter( refreshListenable: MultiFormListenable(_bloc), routes: [ ShellRoute( builder: (context, state, child) { return BlocProvider.value( value: _bloc, child: child, ); }, routes: [ GoRoute( name: 'step1', path: '/step1', builder: (context, state) => Step1(), routes: [ GoRoute( name: 'step2', path: 'step2', builder: (context, state) => Step2(), redirect: (context, state) { if (_bloc.state.step1Value == null) { return '/step1'; } return null; }, ), ], ), ], ), ], );
这种方式直接持有Bloc实例,不需要依赖context,同时能在状态变化时自动触发路由刷新。
内容的提问来源于stack exchange,提问作者Chwizdo
相关产品推荐
相关产品推荐

