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

Flutter中ModalRoute获取当前路由名称始终为/的问题排查

Flutter内部Navigator获取当前RouterSettings.name异常的原因及解决方法

问题描述

作为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),
      ),
    );
  }
}

原因分析

  1. onGenerateRoute硬编码路由名称:内部Navigator的onGenerateRoute方法中,无论传入的settings.name是什么,都固定返回了name: '/'的MaterialPageRoute,导致所有通过该方法生成的路由名称都被强制覆盖为/。
  2. 上下文获取错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 00:06:12