You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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层直接混合操作:

  1. 先确保GoRouter通过Riverpod管理(如果还没做):
final goRouterProvider = Provider<GoRouter>((ref) {
  return GoRouter(
    initialLocation: '/',
    routes: [
      // 你的路由定义
    ],
  );
});
  1. 创建专门处理该业务逻辑的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);
});
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 19:37:18