Flutter 跨层级调用嵌套Navigator跳转路由报错解决方案
问题场景
Flutter项目中使用IndexedStack配合bottomNavigationBar管理4个一级页面,其中第一个页面LocationStateManager内部嵌套了独立Navigator实现子路由管理。底部导航栏除了系统自带的BottomNavigationBar,还放置了自定义组件BottomBarTraveling,效果如下:
需要实现点击BottomBarTraveling时,控制LocationStateManager内部的嵌套Navigator跳转至location_details路由。但BottomBarTraveling位于主Scaffold层级,不属于嵌套Navigator的子节点,直接在点击回调中执行如下代码:
Navigator.pushNamed(context, 'location_details');
会触发路由找不到的报错:
FlutterError (Could not find a generator for route RouteSettings("location_details", null) in the _WidgetsAppState. Make sure your root app widget has provided a way to generate this route. Generators for routes are searched for in the following order: 1. For the "/" route, the "home" property, if non-null, is used. 2. Otherwise, the "routes" table is used, if it has an entry for the route. 3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes". 4. Finally if all else fails onUnknownRoute is called. Unfortunately, onUnknownRoute was not set.)
相关代码
主页面(main.dart)实现
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int currentIndex = 0; @override Widget build(BuildContext context) { final screens = [ LocationStateManager(), MySessionsPage(), MapPage(), SettingsPage() ]; return Scaffold( appBar: AppBar( title: Text(widget.title, style: TextStyle( color: PRIMARY, fontSize: 32, fontFamily: 'JotiOne', fontWeight: FontWeight.bold)), centerTitle: true, backgroundColor: DARK_BACKGROUND_PRIMARY, ), body: SafeArea( child: IndexedStack(index: currentIndex, children: screens), ), bottomNavigationBar: new Theme( data: Theme.of(context).copyWith(canvasColor: DARK_BACKGROUND_PRIMARY), child: Column( mainAxisSize: MainAxisSize.min, children: [ (isVisible) ? BottomBarTraveling() : SizedBox( height: 0, ), BottomNavigationBar( type: BottomNavigationBarType.fixed, iconSize: 30, currentIndex: currentIndex, onTap: (index) => setState(() => currentIndex = index), showSelectedLabels: false, showUnselectedLabels: false, selectedItemColor: NAVBAR_SELECTED, unselectedItemColor: NAVBAR_UNSELECTED, items: [ BottomNavigationBarItem(icon: Icon(Icons.home), label: ''), BottomNavigationBarItem(icon: Icon(Icons.list), label: ''), BottomNavigationBarItem(icon: Icon(Icons.map), label: ''), BottomNavigationBarItem(icon: Icon(Icons.settings), label: '') ], ) ], ), )); } }
嵌套Navigator的LocationStateManager实现
class LocationStateManager extends StatefulWidget { @override _LocationStateManagerState createState() => _LocationStateManagerState(); } class _LocationStateManagerState extends State<LocationStateManager> { @override Widget build(BuildContext context) { return Scaffold( appBar: null, body: Stack(children: <Widget>[ Navigator( onGenerateRoute: (settings) { switch (settings.name) { case 'location_list': return MaterialPageRoute( builder: (context) => LocationListPage(), ); case 'location_details': return MaterialPageRoute( builder: (context) => KiteupLocationPage(), ); case 'kiteup_status_page': return MaterialPageRoute( builder: (context) => KiteupStatusPage(), ); default: return MaterialPageRoute( builder: (context) => LocationListPage(), ); } }, ), ])); } }
解决方案
报错核心原因:直接调用Navigator.of(context)拿到的是根MaterialApp的Navigator,而location_details路由只注册在LocationStateManager内部的嵌套Navigator上,根Navigator找不到对应路由自然抛错。
最稳定无侵入的实现方式是给嵌套Navigator绑定全局Key,外部直接通过Key拿到嵌套Navigator状态做跳转,不受组件层级限制,操作步骤如下:
- 在主页面
_MyHomePageState中声明GlobalKey<NavigatorState>,专门绑定Location页的嵌套Navigator - 将Key通过构造函数传递给
LocationStateManager,绑定到内部的Navigator组件 - 在
BottomBarTraveling的点击回调中,通过Key直接获取嵌套Navigator状态,执行路由跳转
代码修改示例
第一步:主页面声明GlobalKey并传递
class _MyHomePageState extends State<MyHomePage> { int currentIndex = 0; // 声明嵌套Navigator专属全局Key final GlobalKey<NavigatorState> _locationTabNavKey = GlobalKey<NavigatorState>(); @override Widget build(BuildContext context) { final screens = [ // 将Key传入LocationStateManager LocationStateManager(navigatorKey: _locationTabNavKey), MySessionsPage(), MapPage(), SettingsPage() ]; return Scaffold( // 原有AppBar、body代码无需修改 bottomNavigationBar: new Theme( data: Theme.of(context).copyWith(canvasColor: DARK_BACKGROUND_PRIMARY), child: Column( mainAxisSize: MainAxisSize.min, children: [ (isVisible) ? BottomBarTraveling(onTap: () { // 直接通过Key调用嵌套Navigator跳转 _locationTabNavKey.currentState?.pushNamed('location_details'); // 若需要点击后先切回Location tab再跳转,放开下面注释即可 // setState(() => currentIndex = 0); }) : SizedBox(height: 0), // 原有BottomNavigationBar代码无需修改 ], ), )); } }
第二步:修改LocationStateManager接收Key并绑定
class LocationStateManager extends StatefulWidget { // 新增Key接收参数 final GlobalKey<NavigatorState> navigatorKey; const LocationStateManager({Key? key, required this.navigatorKey}) : super(key: key); @override _LocationStateManagerState createState() => _LocationStateManagerState(); } class _LocationStateManagerState extends State<LocationStateManager> { @override Widget build(BuildContext context) { return Scaffold( appBar: null, body: Stack(children: <Widget>[ Navigator( // 绑定传入的Key key: widget.navigatorKey, onGenerateRoute: (settings) { switch (settings.name) { case 'location_list': return MaterialPageRoute( builder: (context) => LocationListPage(), ); case 'location_details': return MaterialPageRoute( builder: (context) => KiteupLocationPage(), ); case 'kiteup_status_page': return MaterialPageRoute( builder: (context) => KiteupStatusPage(), ); default: return MaterialPageRoute( builder: (context) => LocationListPage(), ); } }, ), ])); } }
可选优化
如果需要跳转前清空嵌套Navigator的路由栈,可以将pushNamed替换为pushNamedAndRemoveUntil,按需控制栈状态即可。
内容的提问来源于stack exchange,提问作者A. Vreeswijk
相关产品推荐
相关产品推荐

