使用Riverpod与go_router时,data函数调用context.go触发异常
问题解决:BottomSheet中Riverpod+GoRouter导航触发构建异常
问题本质
触发的异常核心是:导航操作间接触发了Provider状态修改,且该操作与Widget构建周期产生时序冲突。即便导航代码写在onPressed回调中,但嵌套的StateNotifierProvider.when会在状态更新时触发Widget重建,此时执行导航(GoRouter的context.go会触发路由栈变更,可能连带某些Provider的状态更新),就会和当前的Widget构建过程重叠,违反Riverpod禁止在构建周期内修改Provider的规则。
解决方案
方案1:用addPostFrameCallback延迟导航,避开构建周期
将导航逻辑包裹在WidgetsBinding.instance.addPostFrameCallback中,确保在当前帧的Widget构建完全结束后再执行导航:
if ((qty == 0) || !existed) { ref.read(dashboardBottomAppBarIndexProvider.notifier).setIndex(); // 确保在Widget构建完成后执行导航 WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { kIsWeb ? ref.read(navigationProvider.notifier).navigateToPath(context, '/path-a') : ref.read(navigationProvider.notifier).navigateToPath(context, '/path-b'); } }); return; }
注:普通
Future(() {})无法保证避开构建周期,因为它的执行时机仍可能落在当前构建帧内,而addPostFrameCallback是专门用于在帧构建完成后执行的回调。
方案2:将导航逻辑移至Riverpod Notifier层,分离UI与业务逻辑
把导航和状态修改逻辑统一放到Notifier中,避免在Widget层直接混合操作:
- 先确保GoRouter通过Riverpod管理(如果还没做):
final goRouterProvider = Provider<GoRouter>((ref) { return GoRouter( initialLocation: '/', routes: [ // 你的路由定义 ], ); });
- 创建专门处理该业务逻辑的Notifier:
class BottomSheetActionNotifier extends StateNotifier<void> { BottomSheetActionNotifier(this.ref) : super(null); final Ref ref; void handleNavigate(String watchId, int qty, bool existed) { if ((qty == 0) || !existed) { // 更新底部导航栏索引 ref.read(dashboardBottomAppBarIndexProvider.notifier).setIndex(); // 执行导航 final router = ref.read(goRouterProvider); router.go(kIsWeb ? '/path-a' : '/path-b'); } } } final bottomSheetActionProvider = StateNotifierProvider<BottomSheetActionNotifier, void>((ref) { return BottomSheetActionNotifier(ref); });
- 在Widget的
onPressed中调用:
onPressed: () async { if (_selectedString == '') { if (context.mounted) { _dialog.showAutoDismissDialog( context, 'Please select something', CupertinoIcons.exclamationmark_circle_fill, Colors.redAccent); } return; } // 调用Notifier中的方法处理导航 ref.read(bottomSheetActionProvider.notifier).handleNavigate(watchID, qty, existed); if (!(qty == 0 || !existed)) { await _asyncProcess(); } },
方案3:提前提取Provider数据,简化嵌套结构
在build函数外层提前获取Provider数据,减少嵌套的when回调层级,降低时序冲突概率:
@override Widget build(BuildContext context, WidgetRef ref) { final someDataAsync = ref.watch(someDataModelProvider); final anotherDataAsync = ref.watch(someAnotherDataModelProvider); return someDataAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, s) => const Text("Error loading data"), data: (someDataModel) { final existed = (someDataModel.someListData ?? []) .where((item) => item.id == watchID) .firstOrNull?.existed ?? false; return anotherDataAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, s) => const Text("Error loading data"), data: (anotherDataModel) { final qty = (anotherDataModel.someListData ?? []) .where((item) => item.id == watchID) .firstOrNull?.qty ?? 0; void handlePress() async { if (_selectedString.isEmpty) { if (context.mounted) { _dialog.showAutoDismissDialog(...); } return; } if (qty == 0 || !existed) { ref.read(dashboardBottomAppBarIndexProvider.notifier).setIndex(); // 用Future.microtask确保在当前事件循环末尾执行 await Future.microtask(() { if (context.mounted) { kIsWeb ? context.go('/path-a') : context.go('/path-b'); } }); return; } await _asyncProcess(); } return ElevatedButton( onPressed: handlePress, // 按钮样式、文本等属性 ); }, ); }, ); }
内容的提问来源于stack exchange,提问作者DevQt
相关产品推荐
相关产品推荐

