Flutter: go_router 入门

Flutter: go_router 入门(面向 Android 开发者)

目标:用 Android 的路由思维,快速上手 Flutter 的 go_router,从依赖引入 → 初始化 → 路由定义 → 导航与参数 → 守卫/重定向 → 嵌套路由/ShellRoute → 错误页与深链。


1. 依赖引入(pubspec.yaml)

你的项目已存在 go_router(版本可能较低)。建议与现有项目版本保持一致;若新建工程,可使用较新版本:

yaml 复制代码
dependencies:
  go_router: ^14.2.0   # 或与你项目一致的版本
bash 复制代码
flutter pub get

2. 初始化 Router(在应用入口)

dart 复制代码
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(const MyApp());

final GoRouter _router = GoRouter(
  initialLocation: '/',
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      name: 'home',
      builder: (context, state) => const HomePage(),
      routes: <RouteBase>[
        GoRoute(
          path: 'detail/:id', // 完整路径:/detail/:id
          name: 'detail',
          builder: (context, state) {
            final id = state.pathParameters['id']; // 路径参数
            final msg = state.uri.queryParameters['msg']; // 查询参数
            final extra = state.extra as String?; // 额外对象
            return DetailPage(id: id ?? '0', msg: msg, extra: extra);
          },
        ),
      ],
    ),
  ],
  errorBuilder: (context, state) => ErrorPage(error: state.error),
);

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router, // 新写法(旧版可用 routeInformationParser/Delegate)
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light(),
    );
  }
}

3. 页面与导航(常用 API)

dart 复制代码
// HomePage → 导航到详情
class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () {
                // 路径导航(带路径参数与查询参数)
                context.go('/detail/100?msg=hello');
              },
              child: const Text('Go Detail (path + query)'),
            ),
            ElevatedButton(
              onPressed: () {
                // 命名导航 + 参数 + extra
                context.pushNamed(
                  'detail',
                  pathParameters: {'id': '200'},
                  queryParameters: {'msg': 'from-named'},
                  extra: 'EXTRA_PAYLOAD',
                );
              },
              child: const Text('Push Detail (named + extra)'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final String id;
  final String? msg;
  final String? extra;
  const DetailPage({super.key, required this.id, this.msg, this.extra});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail #$id')),
      body: Center(child: Text('msg=$msg, extra=$extra')),
    );
  }
}
  • context.go():替换式导航(相当于 Android 的 replace/clearTop)。
  • context.push():入栈导航(类似 startActivity 入栈)。
  • context.pop():返回。

4. 参数传递(路径/查询/extra)

  • 路径参数:路由定义 path: 'detail/:id',读取 state.pathParameters['id']
  • 查询参数:/detail/1?msg=hi,读取 state.uri.queryParameters['msg']
  • Extra:适合传对象(比如复杂实体),读取 state.extra 并做类型断言。

5. 守卫与重定向(Auth、版本引导等)

最简示例(登录态守卫):

dart 复制代码
class AuthState extends ChangeNotifier {
  bool loggedIn = false;
}
final auth = AuthState();

final GoRouter router = GoRouter(
  refreshListenable: auth, // 登录态变化时触发重定向
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomePage()),
    GoRoute(path: '/login', builder: (_, __) => const LoginPage()),
    GoRoute(path: '/profile', builder: (_, __) => const ProfilePage()),
  ],
  redirect: (context, state) {
    final loggingIn = state.matchedLocation == '/login';
    if (!auth.loggedIn && !loggingIn) {
      // 未登录访问受限页 → 重定向到 /login
      return '/login';
    }
    if (auth.loggedIn && loggingIn) {
      // 已登录不该再看到登录页 → 回首页
      return '/';
    }
    return null; // 不重定向
  },
);

要点:

  • refreshListenable 可以监听状态变化(如登录态变更,自动触发路由刷新/重定向)。
  • redirect 返回 String?,返回非空字符串表示重定向目的地。

6. 嵌套路由与 ShellRoute(底部导航)

dart 复制代码
final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: _indexByLocation(state.matchedLocation),
            onTap: (i) => _goTab(context, i),
            items: const [
              BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
              BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Me'),
            ],
          ),
        );
      },
      routes: [
        GoRoute(path: '/', builder: (_, __) => const HomePage()),
        GoRoute(path: '/me', builder: (_, __) => const MePage()),
      ],
    ),
  ],
);

int _indexByLocation(String loc) => loc.startsWith('/me') ? 1 : 0;
void _goTab(BuildContext ctx, int i) {
  if (i == 0) ctx.go('/'); else ctx.go('/me');
}
  • ShellRoute 允许共享外层 UI(如 BottomNavigationBar),内部切换子路由。

7. 错误页与 404

dart 复制代码
class ErrorPage extends StatelessWidget {
  final Exception? error;
  const ErrorPage({super.key, this.error});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Error')),
      body: Center(child: Text('Oops: ${error?.toString() ?? 'Not Found'}')),
    );
  }
}

8. 深度链接(Deep Link)与 Web 支持

  • MaterialApp.router + GoRouter 天然支持 Web URL。
  • 移动端可结合平台通道/第三方库接入 URI Scheme 或 App Links,然后 context.go() 到目标路径。

9. 常见排错清单

  • "页面不刷新/守卫不生效":确认 refreshListenable 正确传入并在状态变化时调用 notifyListeners()
  • "参数为空/拿不到":路径参数用 pathParameters,查询参数用 state.uri.queryParameters,对象用 extra
  • "返回行为异常":合理区分 go(替换)与 push(入栈),栈结构与返回键行为会不同。
  • "嵌套路由找不到页":确认父子 routes 嵌套关系与 path 写法(子路径不要以斜杠起始时会拼接到父路径后)。

10. 最小模板(可直接复制)

dart 复制代码
final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomePage(), routes: [
      GoRoute(path: 'detail/:id', builder: (_, s) => DetailPage(id: s.pathParameters['id'] ?? '0')),
    ]),
  ],
);

class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('Home')),
        body: Center(
          child: ElevatedButton(
            onPressed: () => context.push('/detail/1?msg=hello'),
            child: const Text('Go Detail'),
          ),
        ),
      );
}

相关推荐
程序员Ctrl喵1 天前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区1 天前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎1 天前
树形选择器组件封装
前端·flutter