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
相关产品推荐
相关产品推荐

