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

Riverpod提供GoRouter时StatefulShellBranch报错求助

问题:Riverpod集成GoRouter+StatefulShellRoute触发路由构建错误

在Flutter项目中通过Riverpod提供GoRouter实例时,使用StatefulShellRoute会触发以下错误:

(pageBuilder != null) ^ (builder != null) One of builder or pageBuilder must be provided, but not both

但使用不含StatefulShellRoute的简单GoRouter配置时可正常运行。


相关代码实现

main函数

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final sharedPreferences = await SharedPreferences.getInstance();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const ProviderScope(child: MyApp()));
}

根组件MyApp

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final goRouter = ref.watch(goRouterProvider);
    return MaterialApp.router(
      routerConfig: goRouter,
      title: 'ARPTC',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
    );
  }
}

触发错误的GoRouter Provider(含StatefulShellRoute)

final goRouterProvider = Provider<GoRouter>((ref) {
  final rootNavigatorKey = GlobalKey<NavigatorState>();
  final _shellNavigatorAKey = GlobalKey<NavigatorState>(debugLabel: 'shellA');
  final _shellNavigatorBKey = GlobalKey<NavigatorState>(debugLabel: 'shellB');

  return GoRouter(
    initialLocation: '/dashboard',
    navigatorKey: rootNavigatorKey,
    debugLogDiagnostics: true,
    routes: [
      StatefulShellRoute.indexedStack(
        branches: [
          StatefulShellBranch(
            navigatorKey: _shellNavigatorAKey,
            routes: [
              GoRoute(
                path: '/a',
                pageBuilder: (context, state) => const NoTransitionPage(
                  child: RootScreen(label: 'A', detailsPath: '/a/details'),
                ),
                routes: [
                  GoRoute(
                    path: 'details',
                    builder: (context, state) => const DetailsScreen(label: 'A'),
                  ),
                ],
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: _shellNavigatorBKey,
            routes: [
              GoRoute(
                path: '/b',
                pageBuilder: (context, state) => const NoTransitionPage(
                  child: RootScreen(label: 'B', detailsPath: '/b/details'),
                ),
                routes: [
                  GoRoute(
                    path: 'details',
                    builder: (context, state) => const DetailsScreen(label: 'B'),
                  ),
                ],
              ),
            ],
          ),
        ],
      )
    ],
    redirect: (context, state) {
      // 注释的认证逻辑
    },
  );
});

可正常运行的简单GoRouter配置

final goRouterProviderTwo = Provider<GoRouter>((ref){
  return router(ref);
});

GoRouter router(ProviderRef ref) {
  final rootNavigatorKey = GlobalKey<NavigatorState>();
  return GoRouter(
    initialLocation: '/courriers',
    navigatorKey: rootNavigatorKey,
    debugLogDiagnostics: true,
    routes: [
      GoRoute(
        path: '/login',
        pageBuilder: (context, state) => const NoTransitionPage(
          child: LoginScreen(),
        ),
      ),
      GoRoute(
        path: '/dashboard',
        pageBuilder: (context, state) => const NoTransitionPage(
          child: DashboardPage(),
        ),
      ),
      GoRoute(
        path: '/courriers',
        pageBuilder: (context, state) => const NoTransitionPage(
          child: ListCourriersScreen(),
        ),
      ),
      GoRoute(
        path: '/courriers/:courrierId',
        pageBuilder: (context, state) {
          return NoTransitionPage(
              child: DetailsCourrierScreen(state.pathParameters['courrierId'] as String));
        },
      ),
      GoRoute(
        path: '/error',
        pageBuilder: (context, state) => const NoTransitionPage(
          child: Center(
            child: Text('Error'),
          ),
        ),
      )
    ],
    redirect: (context, state) async {
      final _authState = ref.watch(authStateProvider);
      return _authState.when(
          data: (data) {
            User? user = data;
            if (user == null ){
              debugPrint(":: GO TO LOGIN SCREEN");
              return '/login';
            }
          },
          loading: () => '/login',
          error: (e, trace) => '/error');
    },
  );
}

问题原因与解决方法

1. 核心错误原因

错误提示明确要求:每个GoRoute必须且只能提供builder或pageBuilder中的一个。在StatefulShellRoute的分支路由体系中,所有嵌套路由必须统一使用pageBuilder——因为Shell路由基于页面栈管理状态,需要通过Page组件来维护路由栈的状态一致性,不能混用builder。

2. 额外问题:初始路径不匹配

你的initialLocation设置为/dashboard,但当前StatefulShellRoute的分支中没有该路径,会导致路由初始化失败,需要修改为分支中存在的路径(如/a)。

修正后的GoRouter Provider代码

final goRouterProvider = Provider<GoRouter>((ref) {
  final rootNavigatorKey = GlobalKey<NavigatorState>();
  final _shellNavigatorAKey = GlobalKey<NavigatorState>(debugLabel: 'shellA');
  final _shellNavigatorBKey = GlobalKey<NavigatorState>(debugLabel: 'shellB');

  return GoRouter(
    initialLocation: '/a', // 修改为分支中存在的路径
    navigatorKey: rootNavigatorKey,
    debugLogDiagnostics: true,
    routes: [
      StatefulShellRoute.indexedStack(
        branches: [
          StatefulShellBranch(
            navigatorKey: _shellNavigatorAKey,
            routes: [
              GoRoute(
                path: '/a',
                pageBuilder: (context, state) => const NoTransitionPage(
                  child: RootScreen(label: 'A', detailsPath: '/a/details'),
                ),
                routes: [
                  GoRoute(
                    path: 'details',
                    // 将builder替换为pageBuilder,用NoTransitionPage包裹
                    pageBuilder: (context, state) => const NoTransitionPage(
                      child: DetailsScreen(label: 'A'),
                    ),
                  ),
                ],
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: _shellNavigatorBKey,
            routes: [
              GoRoute(
                path: '/b',
                pageBuilder: (context, state) => const NoTransitionPage(
                  child: RootScreen(label: 'B', detailsPath: '/b/details'),
                ),
                routes: [
                  GoRoute(
                    path: 'details',
                    pageBuilder: (context, state) => const NoTransitionPage(
                      child: DetailsScreen(label: 'B'),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ],
      )
    ],
    redirect: (context, state) {
      // 认证逻辑可按需恢复
    },
  );
});

内容的提问来源于stack exchange,提问作者Armando Sudi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:45:02