Flutter中ModalRoute获取当前路由名称始终为/的问题排查
问题描述
作为Flutter新手,在实现侧边栏导航的示例代码中,尝试通过ModalRoute.of(navKey.currentContext!)?.settings.name获取当前路由名称以避免重复跳转,但该值始终返回/。希望了解该现象的原因,以及正确获取当前RouterSettings的方法。
示例代码
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(), ); } } class Menu { /// 路由名称 final String name; /// 菜单标题 final String title; final IconData icon; bool selected; Widget Function(BuildContext)? _builder; Widget Function(BuildContext) get builder => _builder ?? (context) => ContentPage(title: title); set builder(Widget Function(BuildContext) b) => _builder = b; Menu({ required this.name, required this.title, required this.icon, this.selected = false, Widget Function(BuildContext)? builder, }) : _builder = builder; } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final List<Menu> menus = [ Menu(name: '/', title: 'Dashboard', icon: Icons.dashboard, selected: true), Menu(name: '/Users', title: 'Users', icon: Icons.people), Menu(name: '/Orders', title: 'Orders', icon: Icons.list_alt), Menu(name: '/PersonalCentral', title: 'Mine', icon: Icons.person), ]; final GlobalKey<NavigatorState> navKey = GlobalKey(); void _onMenuTap(Menu m) { final currentName = ModalRoute.of(navKey.currentContext!)?.settings.name; debugPrint('_onMenuTap $currentName'); // 问题:currentName始终为 `/` if (currentName == m.name) { // 避免重复跳转 return; } navKey.currentState?.push( MaterialPageRoute(settings: RouteSettings(name: m.name), builder: m.builder), ); setState(() { menus.where((m1) => m1.selected).firstOrNull?.selected = false; m.selected = true; }); } @override Widget build(BuildContext context) { return Scaffold( body: Row( children: [ Expanded( flex: 3, child: ListView( children: menus.map( (m) { return ListTile( leading: Icon(m.icon), title: Text(m.title), selected: m.selected, selectedColor: Theme.of(context).listTileTheme.selectedColor ?? Colors.lightBlue, onTap: () => _onMenuTap(m), ); }, ).toList(growable: false), ), ), Expanded( flex: 9, child: Navigator( key: navKey, onGenerateRoute: (settings) { // 会打印 `onGenerateRoute /` debugPrint("onGenerateRoute ${settings.name}"); return MaterialPageRoute( settings: const RouteSettings(name: '/'), builder: (context) => const ContentPage(title: 'Dashboard'), ); }, ), ), ], ), ); } } class ContentPage extends StatelessWidget { final String title; const ContentPage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Text(title), ), ); } }
原因分析
- onGenerateRoute硬编码路由名称:内部
Navigator的onGenerateRoute方法中,无论传入的settings.name是什么,都固定返回了name: '/'的MaterialPageRoute,导致所有通过该方法生成的路由名称都被强制覆盖为/。 - 上下文获取错误:
navKey.currentContext指向的是内部Navigator组件自身的上下文,该上下文对应的是外层MaterialApp导航栈中的MyHomePage路由,而非内部Navigator栈中的活跃路由。因此ModalRoute.of(navKey.currentContext!)拿到的始终是外层路由的名称/,而非内部栈顶的实际路由名称。
解决方法
1. 修复onGenerateRoute,保留正确的路由名称
修改内部Navigator的onGenerateRoute逻辑,不再硬编码RouteSettings.name,而是复用传入的settings参数,同时根据路由名称匹配对应的菜单页面:
onGenerateRoute: (settings) { debugPrint("onGenerateRoute ${settings.name}"); // 根据路由名称匹配对应的菜单 final targetMenu = menus.firstWhere( (menu) => menu.name == settings.name, orElse: () => menus[0], ); return MaterialPageRoute( settings: settings, // 保留传入的settings,包含正确的路由名称 builder: targetMenu.builder, ); }
2. 正确获取内部Navigator的栈顶路由名称
在_onMenuTap方法中,通过navKey.currentState?.currentRoute直接获取内部Navigator栈顶的路由,进而拿到正确的路由名称:
void _onMenuTap(Menu m) { // 获取内部Navigator栈顶的路由名称 final currentName = navKey.currentState?.currentRoute?.settings.name; debugPrint('_onMenuTap $currentName'); if (currentName == m.name) { return; } navKey.currentState?.push( MaterialPageRoute(settings: RouteSettings(name: m.name), builder: m.builder), ); setState(() { menus.where((m1) => m1.selected).firstOrNull?.selected = false; m.selected = true; }); }
补充说明
如果希望替换路由而非叠加路由(避免导航栈过深),可以使用pushReplacement代替push,这种方式下导航栈始终只有一个页面,也能自然避免重复跳转。
内容的提问来源于stack exchange,提问作者coolboy
相关产品推荐
相关产品推荐

