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

Flutter/GoRouter问题:重置StatefulShellRoute分支至初始路由

问题描述

我在项目中使用GoRouter处理导航,通过StatefulShellRoute管理底部导航栏,路由配置如下:

List<RouteBase> get routes => [
        
        StatefulShellRoute.indexedStack(
          builder: (context, state, navigationShell) =>
              TabBarPage(navigationShell: navigationShell),
          branches: [
            StatefulShellBranch(
              navigatorKey: _rootNavigatorHome,
              initialLocation: HomePage.path,
              routes: [
                _homeTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorCatalog,
              initialLocation: CatalogPage.path,
              routes: [
                _catalogTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorCart,
              routes: [
                _cartTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorOrders,
              routes: [
                _ordersTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorProfile,
              routes: [
                _profileTab,
              ],
            ),
          ],
        ),
      ];

当用户切换当前办公地点时,需要将所有标签页(分支)恢复到初始路由。我通过Riverpod监听办公地点变更,代码如下:

ref.listen(currentOfficeProvider, (prev, current) {
      if (prev == current) return;
      final branches = navigationShell.route.branches;
      for (final branch in branches) {
        branch.navigatorKey.currentState?.popUntil((route) => route.isFirst);
      }
  });

但每次执行都会触发以下错误:

Unhandled Exception: 'package:go_router/src/match.dart': Failed assertion: line 265 pos 12: 'index != -1': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
#2      RouteMatchList.remove (package:go_router/src/match.dart:265:12)
#3      GoRouterDelegate._handlePopPageWithRouteMatch (package:go_router/src/delegate.dart:105:49)
#4      _PagePopContext.onPopPage (package:go_router/src/builder.dart:572:35)
#5      NavigatorState.pop (package:flutter/src/widgets/navigator.dart:5045:28)
#6      NavigatorState.popUntil (package:flutter/src/widgets/navigator.dart:5085:7)
#7      TabBarPage.build.<anonymous closure> (package:yulsun_app/feachers/tab_bar/tab_bar_page.dart:59:43)
#8      _RootZone.runBinaryGuarded (dart:async/zone.dart:1606:10)
#9      ProviderElementBase._notifyListeners.<anonymous closure> (package:riverpod/src/framework/element.dart:537:24)
#10     ResultData.map (package:riverpod/src/result.dart:74:16)
#11     ProviderElementBase._notifyListeners (package:riverpod/src/framework/element.dart:534:14)
#12     ProviderElementBase._performBuild (package:riverpod/src/framework/element.dart:374:7)
#13     ProviderElementBase.flush (package:riverpod/src/framework/element.dart:325:7)
#14     _ProviderScheduler._performRefresh (package:riverpod/src/framework/scheduler.dart:62:41)
#15     _ProviderScheduler._task (package:riverpod/src/framework/scheduler.dart:50:5)
#16     _UncontrolledProviderScopeElement.build (package:flutter_riverpod/src/framework.dart:392:12)
#17     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5297:15)
#18     Element.rebuild (package:flutter/src/widgets/framework.dart:5016:7)
#19     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2779:19)
#20     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:916:21)
#21     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:360:5)
#22     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1297:15)
#23     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1227:9)
#24     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1085:5)
#25     _invoke (dart:ui/hooks.dart:170:13)
#26     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:401:5)
#27     _drawFrame (dart:ui/hooks.dart:140:31)

请问问题出在哪里?我猜测Flutter无法重绘未显示的页面,但这种情况下该如何重置所有分支?


问题分析与解决方案

问题原因

直接调用分支导航器的popUntil会与GoRouter的路由管理逻辑冲突:

  • StatefulShellRoute的分支导航器由GoRouter内部管理,未显示的分支(IndexedStack中未激活的页面)其路由栈状态未与GoRouter的路由匹配列表同步,强行执行popUntil会导致GoRouter找不到对应路由索引,触发断言错误。
  • 部分分支未配置initialLocation,GoRouter无法确定其初始路由的匹配位置,进一步加剧状态不一致问题。

正确的重置方式

不要直接操作分支导航器,使用GoRouter提供的专属API来重置分支路由:

1. 补全所有分支的initialLocation

给每个StatefulShellBranch添加明确的初始路径,确保GoRouter能识别分支的初始路由:

StatefulShellBranch(
  navigatorKey: _rootNavigatorCart,
  initialLocation: CartPage.path, // 补充初始路径
  routes: [
    _cartTab,
  ],
),
// 其他分支同理补全initialLocation

2. 通过goBranch重置所有分支

使用navigationShell.goBranch方法,直接跳转到每个分支的初始位置,通过replaceCurrent: true参数重置路由栈:

ref.listen(currentOfficeProvider, (prev, current) {
  if (prev == current) return;
  // 遍历所有分支索引
  for (int i = 0; i < navigationShell.route.branches.length; i++) {
    final initialLocation = navigationShell.route.branches[i].initialLocation;
    if (initialLocation != null) {
      // 重置分支路由栈为初始状态
      navigationShell.goBranch(
        i,
        initialLocation: initialLocation,
        replaceCurrent: true,
      );
    }
  }
});

3. 保持当前激活标签页不变

如果需要重置所有分支但保留当前选中的标签页,可先记录当前索引,重置后切回:

ref.listen(currentOfficeProvider, (prev, current) {
  if (prev == current) return;
  final currentIndex = navigationShell.currentIndex;
  // 重置所有分支
  for (int i = 0; i < navigationShell.route.branches.length; i++) {
    final initialLocation = navigationShell.route.branches[i].initialLocation;
    if (initialLocation != null) {
      navigationShell.goBranch(
        i,
        initialLocation: initialLocation,
        replaceCurrent: true,
      );
    }
  }
  // 切回之前选中的标签页
  navigationShell.goBranch(currentIndex);
});

方案优势

goBranch是GoRouter为StatefulShellRoute设计的专属API,能正确同步GoRouter的路由匹配列表与分支导航器的状态,避免直接操作导航器导致的状态不一致问题。replaceCurrent: true参数会将分支路由栈替换为初始路由,完美实现重置效果且不会触发断言错误。


内容的提问来源于stack exchange,提问作者Angelina Gromova

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:27:04