Flutter go_router 路由管理详解&封装

go_router 是一个用于 Flutter 应用的第三方路由管理库,它简化了应用内的路由导航逻辑,提供了声明式的路由配置方式,同时对 URL 有很好的支持,在 Web、移动端和桌面端都能表现出色。开始了解以前,你可以先看一下原生路由导航:Flutter 路由与导航

go_router特性

  • GoRouter的配置(routes, redirect, errorBuilder)
  • 导航方法(go, push, pop)
  • 命名路由(goNamed, pushNamed)
  • 路由参数传递(queryParams, extra)
  • 路由守卫(redirect函数)
  • 错误处理(errorBuilder)
  • 路由状态获取(location, route名称)
  • 嵌套路由(ShellRoute)
  • 监听路由变化

1、 路由配置

GoRoutergo_router 库的核心类,用于配置路由信息。

GoRouter 构造函数

swift 复制代码
GoRouter({
  required List<RouteBase> routes,
  GoRouterRedirect? redirect,
  List<NavigatorObserver>? observers,
  GlobalKey<NavigatorState>? navigatorKey,
  String? initialLocation,
  bool? debugLogDiagnostics,
  RouteInformationParser<Uri>? routeInformationParser,
  RouterDelegate<Uri>? routerDelegate,
  BackButtonDispatcher? backButtonDispatcher,
  String restorationScopeId,
})
  • routes:必填参数,用于定义应用的路由列表。
  • redirect:可选参数,用于在导航时进行重定向。
  • observers:可选参数,用于监听导航事件的观察者列表。
  • navigatorKey:可选参数,用于访问底层的 Navigator 实例。
  • initialLocation:可选参数,指定应用启动时的初始路由。
  • debugLogDiagnostics:可选参数,是否在调试模式下打印路由诊断信息。

使用示例

php 复制代码
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomePage();
      },
    ),
    GoRoute(
      path: '/details',
      builder: (BuildContext context, GoRouterState state) {
        return const DetailsPage();
      },
    ),
  ],
);

GoRoute 路由定义

dart 复制代码
GoRoute({
  required String path,
  required WidgetBuilder builder,
  List<RouteBase>? routes,
  GoRouterRedirect? redirect,
  LocalKey? key,
  String? name,
  PageBuilder? pageBuilder,
  bool maintainState = true,
  bool fullscreenDialog = false,
})
  • required String path:这是一个必填参数,用于指定该路由对应的路径。路径可以是固定的字符串,也可以包含路径参数。路径参数使用冒号 : 开头,用于匹配动态的值。

  • required WidgetBuilder builder:同样是必填参数,它是一个函数,用于构建该路由对应的 Widget。该函数接收两个参数:BuildContext contextGoRouterState state,并返回一个 Widget

    csharp 复制代码
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomePage();
      },
    )
  • List<RouteBase>? routes:可选参数,用于定义该路由的子路由列表。子路由可以进一步细分当前路由的导航结构。

    less 复制代码
    GoRoute(
      path: '/settings',
      builder: (context, state) => SettingsPage(),
      routes: [
        GoRoute(
          path: 'notifications',
          builder: (context, state) => NotificationSettingsPage(),
        ),
      ],
    )
    • /settings 是父路由,/settings/notifications 是它的子路由。
  • GoRouterRedirect? redirect:是一个重定向函数。该函数接收 BuildContext contextGoRouterState state 作为参数,并返回一个字符串或 null。如果返回一个字符串,则表示重定向到该路径;如果返回 null,则正常导航到当前路由。

    javascript 复制代码
    GoRoute(
      path: '/secret',
      redirect: (context, state) {
        // 假设 isUserAuthenticated 是一个检查用户是否认证的函数
        if (!isUserAuthenticated()) {
          return '/login';
        }
        return null;
      },
      builder: (context, state) => SecretPage(),
    )

    如果用户未认证,访问 /secret 路径时会重定向到 /login 路径。

  • LocalKey? key:于给该路由的 Widget 提供一个唯一的键。键可以帮助 Flutter 更准确地识别和更新 Widget

  • String? name :为该路由指定一个名称。通过名称可以更方便地进行导航,而不需要记住具体的路径。

  • pageBuilder:是一个用于构建 Page 对象的函数。与 builder 不同,pageBuilder 可以自定义页面的过渡效果等。如果同时提供了 builderpageBuilderpageBuilder 会优先使用。

    less 复制代码
    GoRoute(
      path: '/details',
      pageBuilder: (context, state) {
        return MaterialPage(
          key: state.pageKey,
          child: DetailsPage(),
        );
      },
    )
  • maintainState = true:表示当该路由离开导航栈时,是否保留其状态。如果设置为 true,当再次返回该路由时,会恢复之前的状态;如果设置为 false,每次进入该路由都会重新创建 Widget

  • fullscreenDialog = false: 表示该路由是否以全屏对话框的形式显示。如果设置为 true,在 Android 上会以全屏对话框的样式显示,在 iOS 上会有不同的过渡效果。

路由参数

GoRouter 的每一个路由都通过 GoRoute对象来配置,我们可以在构建 GoRoute 对象时来配置路由参数。路由参数典型的就是路径参数,比如 /path/:{路径参数},这个时候 GoRoute的路径参数和很多 Web 框架的路由是一样的,通过一个英文冒号加参数名称就可以配置,之后我们可以在回调方法中通过 GoRouterState 对象获取路径参数,这个参数就可以传递到路由跳转目的页面。

1、路径参数(Path Parameters)

路径参数用于在路由路径中传递动态值,例如用户 ID、文章 ID 等。在定义路由时,使用冒号 : 来标记路径参数。

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

// 定义一个接收路径参数的页面
class UserPage extends StatelessWidget {
  final String userId;

  const UserPage({Key? key, required this.userId}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User $userId')),
      body: Center(
        child: Text('User ID: $userId'),
      ),
    );
  }
}

// 配置 GoRouter,定义包含路径参数的路由
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/users/:id',
      builder: (BuildContext context, GoRouterState state) {
        // 从 GoRouterState 中获取路径参数
        final String userId = state.params['id']!;
        return UserPage(userId: userId);
      },
    ),
  ],
);

void main() {
  runApp(
    MaterialApp.router(
      routerConfig: _router,
    ),
  );
}

导航到包含路径参数的路由

go 复制代码
// 导航到包含路径参数的路由
GoRouter.of(context).go('/users/123');

2、查询参数(Query Parameters)

查询参数用于在 URL 中传递额外的信息,通常用于过滤、排序等操作。查询参数以问号 ? 开头,多个参数之间用 & 分隔。

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

// 定义一个接收查询参数的页面
class SearchPage extends StatelessWidget {
  final String? query;

  const SearchPage({Key? key, this.query}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Search')),
      body: Center(
        child: Text('Search query: ${query ?? 'No query'}'),
      ),
    );
  }
}

// 配置 GoRouter,定义接收查询参数的路由
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/search',
      builder: (BuildContext context, GoRouterState state) {
        // 从 GoRouterState 中获取查询参数
        final String? query = state.queryParams['q'];
        return SearchPage(query: query);
      },
    ),
  ],
);

void main() {
  runApp(
    MaterialApp.router(
      routerConfig: _router,
    ),
  );
}

导航到包含查询参数的路由

go 复制代码
// 导航到包含查询参数的路由
GoRouter.of(context).go('/search?q=flutter');

3、命名路由与参数传递

php 复制代码
// 定义带名称的路由,包含路径参数
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      name: 'user',
      path: '/users/:id',
      builder: (BuildContext context, GoRouterState state) {
        final String userId = state.params['id']!;
        return UserPage(userId: userId);
      },
    ),
  ],
);

// 通过名称导航并传递路径参数
GoRouter.of(context).goNamed('user', params: {'id': '456'});

// 通过名称导航并传递查询参数
GoRouter.of(context).goNamed('search', queryParams: {'q': 'dart'});

2、 导航方法

基本配置

context.go

直接导航到指定的路由,会替换当前的路由栈,即当前页面会被新页面替换。

go 复制代码
context.go('/details');

context.push

将新的路由页面推送到路由栈中,当前页面不会被替换,用户可以通过返回操作回到上一个页面。

arduino 复制代码
context.push('/details');

context.pop

从路由栈中弹出当前页面,返回到上一个页面。

ini 复制代码
context.pop();

context.goNamedcontext.pushNamed

当你为路由配置了名称时,可以使用这两个方法通过名称进行导航。

php 复制代码
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      name: 'home',
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        return const HomePage();
      },
      routes: <RouteBase>[
        GoRoute(
          name: 'details',
          path: 'details',
          builder: (BuildContext context, GoRouterState state) {
            return const DetailsPage();
          },
        ),
      ],
    ),
  ],
);

// 使用名称导航
context.goNamed('details');
context.pushNamed('details');

嵌套导航 ShellRoute

go_router 中,嵌套导航允许你在应用的某个部分实现独立的路由管理,例如在底部导航栏或者侧边栏的不同标签页内进行各自的路由切换。以下是关于 go_router 嵌套导航的详细介绍和示例代码。

less 复制代码
ShellRoute(
  builder: (context, state, child) => Scaffold(
    body: child,
    bottomNavigationBar: BottomNavBar(),
  ),
  routes: [
    GoRoute(path: '/books', ...),
    GoRoute(path: '/movies', ...),
  ],
)

实现思路

  1. 外层路由配置:定义整个应用的主要路由结构,包含嵌套导航的父路由。
  2. 内层路由配置:在父路由内部定义子路由,用于管理嵌套导航的页面。
  3. 使用 ShellRouteShellRoutego_router 中用于实现嵌套导航的关键组件,它可以包裹子路由,提供一个共享的布局。
less 复制代码
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

// 定义页面
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                context.go('/books');
              },
              child: const Text('Go to Books'),
            ),
            ElevatedButton(
              onPressed: () {
                context.go('/movies');
              },
              child: const Text('Go to Movies'),
            ),
          ],
        ),
      ),
    );
  }
}

class BooksPage extends StatelessWidget {
  const BooksPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Books')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.go('/books/details');
          },
          child: const Text('Go to Book Details'),
        ),
      ),
    );
  }
}

class BookDetailsPage extends StatelessWidget {
  const BookDetailsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Book Details')),
      body: const Center(child: Text('This is the book details page.')),
    );
  }
}

class MoviesPage extends StatelessWidget {
  const MoviesPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Movies')),
      body: const Center(child: Text('This is the movies page.')),
    );
  }
}

// 配置路由
final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
    ShellRoute(
      builder: (context, state, child) {
        return Scaffold(
          body: child,
        );
      },
      routes: [
        GoRoute(
          path: '/books',
          builder: (context, state) => const BooksPage(),
          routes: [
            GoRoute(
              path: 'details',
              builder: (context, state) => const BookDetailsPage(),
            ),
          ],
        ),
        GoRoute(
          path: '/movies',
          builder: (context, state) => const MoviesPage(),
        ),
      ],
    ),
  ],
);

void main() {
  runApp(
    MaterialApp.router(
      routerConfig: _router,
    ),
  );
}
  • 1、外层路由

    • 定义了根路由 /,对应 HomePage,这是应用的起始页面。
  • 2、ShellRoute

    • ShellRoute 包裹了两个子路由 /books/movies,它的 builder 方法返回一个 Scaffold,用于提供一个共享的布局。

    • 子路由的页面会显示在 Scaffoldbody 中。

  • 内层路由

    • /books 路由下还有一个子路由 /books/details,对应 BookDetailsPage,实现了在 BooksPage 内部的嵌套导航。

3、 路由守卫与拦截

php 复制代码
// 路由重定向函数
String? redirectLogic(GoRouterState state) {
  final bool isGoingToLogin = state.matchedLocation == '/login';
  // 如果用户未登录且不是前往登录页,则重定向到登录页
  if (!isUserLoggedIn && !isGoingToLogin) {
    return '/login';
  }
  // 如果用户已登录且正在前往登录页,则重定向到仪表盘页
  if (isUserLoggedIn && isGoingToLogin) {
    return '/dashboard';
  }
  return null; // 允许导航到目标路由
}

// 配置路由
final GoRouter _router = GoRouter(
  redirect: redirectLogic,
  routes: [
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginPage(),
    ),
    GoRoute(
      path: '/dashboard',
      builder: (context, state) => const DashboardPage(),
    ),
  ],
);
  • 模拟用户登录状态 :使用 isUserLoggedIn 布尔变量来模拟用户的登录状态。
  • redirectLogic 函数
    • 该函数接收 GoRouterState 对象,该对象包含了当前导航的相关信息,如目标路由的路径。
    • 当用户未登录且尝试访问除登录页之外的页面时,函数返回 /login,将用户重定向到登录页。
    • 当用户已登录且尝试访问登录页时,函数返回 /dashboard,将用户重定向到仪表盘页。
    • 如果不需要重定向,函数返回 null,允许用户正常导航到目标路由。
  • GoRouter 配置 :在 GoRouter 的构造函数中传入 redirect 参数,将其设置为 redirectLogic 函数,这样每次导航前都会执行该函数的逻辑。

4、 错误处理

less 复制代码
final GoRouter _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
  ],
  errorBuilder: (context, state) {
    return ErrorPage(error: state.error);
  },
);
  • errorBuilder 参数指定了一个函数,当导航出错时会调用这个函数。该函数接收 contextstate 两个参数,state.error 包含了具体的错误信息,我们将其传递给 ErrorPage 来显示。

上述示例主要处理了导航到不存在路由的情况,也就是 404 错误。当用户尝试访问未在 routes 中定义的路由时,go_router 会触发 errorBuilder 来显示错误页面。

除了 404 错误,在实际开发中还可能遇到其他类型的错误,比如在路由的 builder 函数中抛出异常。这些错误同样会触发 errorBuilder,你可以根据 state.error 的具体类型进行不同的处理,例如:

vbnet 复制代码
errorBuilder: (context, state) {
  if (state.error is SomeSpecificException) {
    // 处理特定类型的异常
    return SpecificErrorPage(error: state.error);
  }
  return ErrorPage(error: state.error);
}

5、路由状态获取

1、在路由构建器中获取状态

GoRoutebuilderpageBuilder 函数中,会传入一个 GoRouterState 对象,你可以通过它来获取路由的相关信息。

ini 复制代码
GoRoute(
  path: 'details/:id',
  builder: (BuildContext context, GoRouterState state) {
    // 获取路径参数
    final String id = state.pathParameters['id']!;

    // 获取查询参数
    final String? queryParam = state.queryParameters['param'];

    // 获取完整的 URI
    final Uri uri = state.uri;

    return DetailsPage(id: id);
  },
);

2 在 Widget 中获取当前路由状态

scala 复制代码
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final GoRouterState state = GoRouterState.of(context);
    final String? currentPath = state.matchedLocation;

    return Text('Current path: $currentPath');
  }
}

7、监听路由变化

1、使用 GoRouter refreshListenable

GoRouter 提供了 refreshListenable 选项,你可以传入一个 Listenable 对象,当路由发生变化时,GoRouter 会通知这个 Listenable,进而触发更新。

less 复制代码
/ 创建一个 ValueNotifier 作为可监听对象
final ValueNotifier<String> routeNotifier = ValueNotifier<String>('');

// 定义路由
final GoRouter _router = GoRouter(
  refreshListenable: routeNotifier,
  routes: <GoRoute>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) {
        // 更新路由信息
        routeNotifier.value = state.matchedLocation;
        return const HomePage();
      },
      routes: <GoRoute>[
        GoRoute(
          path: 'details/:id',
          builder: (BuildContext context, GoRouterState state) {
            // 更新路由信息
            routeNotifier.value = state.matchedLocation;
            final String id = state.pathParameters['id']!;
            return DetailsPage(id: id);
          },
        ),
      ],
    ),
  ],
);



// 监听路由变化的 Widget
class RouteListenerWidget extends StatelessWidget {
  const RouteListenerWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<String>(
      valueListenable: routeNotifier,
      builder: (BuildContext context, String value, Widget? child) {
        return Text('Current Route: $value');
      },
    );
  }
}

void main() {
  runApp(
    MaterialApp.router(
      routerConfig: _router,
      home: Column(
        children: [
          const RouteListenerWidget(),
          Expanded(
            child: Router(
              routerConfig: _router,
            ),
          ),
        ],
      ),
    ),
  );
}
  • 首先创建了一个 ValueNotifier<String> 类型的 routeNotifier 作为可监听对象,并将其传递给 GoRouterrefreshListenable 属性。
  • 在每个路由的 builder 函数中,更新 routeNotifier 的值为当前匹配的路径。
  • 创建了一个 RouteListenerWidget,使用 ValueListenableBuilder 监听 routeNotifier 的变化,并在路由变化时更新显示的路由信息。

2、使用 GoRouterobservers

dart 复制代码
class RouterObserver extends NavigatorObserver {
  void log(value) => debugPrint('MyNavObserver:$value');

  /// 当一个新的路由被推送到导航栈时,此方法会被调用。
  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('新的路由被推送到导航栈: ${route.toString()}, previousRoute= ${previousRoute?.toString()}');
  }

  /// 当一个路由从导航栈中弹出时,此方法会被调用。route 参数表示被弹出的路由,previousRoute 参数
  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('路由从导航栈中弹出: ${route.toString()}, previousRoute= ${previousRoute?.toString()}');
  }

  /// 当一个路由从导航栈中被移除时,此方法会被调用。移除路由和弹出路由不同,移除操作可以移除导航栈中任意位置的路由,而弹出操作只能移除栈顶的路由。
  /// route 参数表示被移除的路由,previousRoute 参数表示在该路由移除后,其下一个路由(如果存在的话)。
  @override
  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('didRemove: ${route.toString()}, previousRoute= ${previousRoute?.toString()}');
  }

  @override
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    log('didReplace: new= ${newRoute?.toString()}, old= ${oldRoute?.toString()}');
  }

  /// 当用户开始进行一个导航手势(如在 iOS 上从屏幕边缘向左滑动返回上一页)时,此方法会被调用。
  /// route 参数表示当前正在操作的路由,previousRoute 参数表示在手势操作后可能会显示的前一个路由(如果存在的话)。
  @override
  void didStartUserGesture(
      Route<dynamic> route, Route<dynamic>? previousRoute) {
    log('didStartUserGesture: ${route.toString()}, '
        'previousRoute= ${previousRoute?.toString()}');
  }

  /// 用户结束导航手势时,此方法会被调用。无论手势是否成功完成导航操作,只要手势结束,就会触发这个方法。
  @override
  void didStopUserGesture() {
    log('didStopUserGesture');
  }
}

8. 其他实用 API

  • 刷新路由router.refresh()(常用于登录状态变化后强制重定向)。
  • 获取当前路径final location = router.location();
  • 获取路由名称final routeName = router.routeInformationProvider.value.name;

路由封装

go_router是一个声明式的路由库,支持深度链接和导航,适合复杂的路由场景。用户可能已经了解了基础的路由配置,但现在需要将路由配置进行封装,以提高代码的可维护性和扩展性。

  1. 路由配置集中管理:将所有路由路径、名称定义在一个单独的类或文件中,避免在代码中散落字符串,减少错误。
  2. 模块化路由配置:将不同功能模块的路由分别封装到不同的文件中,便于团队协作和模块化开发。
  3. 路由守卫封装:统一处理路由跳转前的权限验证或条件检查,例如登录状态检查。
  4. 路由跳转方法封装:提供统一的跳转方法,简化上下文的使用,特别是无需context的情况。
  5. 自定义路由过渡动画:封装路由跳转的动画效果,保持应用内的一致性。

1. 路由路径集中管理

将路由路径和名称统一管理,避免硬编码:

ini 复制代码
// lib/routes/app_routes.dart

abstract class AppRoutes {
  static const home = '/';
  static const details = '/details';
  static const profile = '/profile';
  static const settings = '/settings';
}

2. 模块化路由配置

将不同模块的路由定义拆分到独立文件中:

dart 复制代码
// lib/routes/home_route.dart
import 'package:go_router/go_router.dart';
import '../pages/home_page.dart';

GoRoute get homeRoute => GoRoute(
  path: AppRoutes.home,
  pageBuilder: (context, state) => MaterialPage(
    key: state.pageKey,
    child: const HomePage(),
  ),
);

// lib/routes/details_route.dart
import 'package:go_router/go_router.dart';
import '../pages/details_page.dart';

GoRoute get detailsRoute => GoRoute(
  path: AppRoutes.details,
  pageBuilder: (context, state) => MaterialPage(
    key: state.pageKey,
    child: const DetailsPage(),
  ),
);

3. 全局路由配置整合

统一整合所有模块路由:

arduino 复制代码
// lib/routes/router_config.dart
import 'package:go_router/go_router.dart';
import 'home_route.dart';
import 'details_route.dart';

final appRouter = GoRouter(
  initialLocation: AppRoutes.home,
  routes: [
    homeRoute,
    detailsRoute,
    // 添加更多路由...
  ],
);

4. 路由守卫封装

实现全局路由守卫(例如登录验证):

arduino 复制代码
// lib/routes/route_guard.dart
import 'package:go_router/go_router.dart';

class RouteGuard {
  static bool isLoggedIn = false;

  static FutureOr<String?> authGuard(
    BuildContext context,
    GoRouterState state,
  ) {
    final isLoginPage = state.location == AppRoutes.login;
    
    if (!isLoggedIn && !isLoginPage) {
      return AppRoutes.login; // 跳转登录页
    }
    
    if (isLoggedIn && isLoginPage) {
      return AppRoutes.home; // 已登录时禁止返回登录页
    }
    
    return null; // 允许导航
  }
}

// 在路由配置中启用
final appRouter = GoRouter(
  redirect: RouteGuard.authGuard,
  // ...其他配置
);

5. 路由跳转工具类

封装无需 context 的跳转方法:

javascript 复制代码
// lib/utils/navigation_service.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class NavigationService {
  static final GlobalKey<NavigatorState> navigatorKey = 
    GlobalKey<NavigatorState>();

  static BuildContext get context => 
    navigatorKey.currentState!.context;

  static void pushNamed(String routeName, {Object? extra}) {
    context.pushNamed(routeName, extra: extra);
  }

  static void goNamed(String routeName, {Object? extra}) {
    context.goNamed(routeName, extra: extra);
  }

  static void pop() => context.pop();
}

// 在 MaterialApp 中注入
MaterialApp.router(
  routerConfig: appRouter,
  navigatorKey: NavigationService.navigatorKey,
);

6. 参数传递标准化

定义统一参数传递模型

kotlin 复制代码
// lib/models/route_args.dart
class DetailsPageArgs {
  final String id;
  final String? source;

  DetailsPageArgs({
    required this.id,
    this.source,
  });
}

// 使用示例
NavigationService.pushNamed(
  AppRoutes.details,
  extra: DetailsPageArgs(id: '123', source: 'home'),
);

// 在页面中获取参数
final args = state.extra as DetailsPageArgs;

7 错误路由处理

统一404页面处理:

less 复制代码
final appRouter = GoRouter(
  errorPageBuilder: (context, state) => MaterialPage(
    child: Scaffold(
      body: Center(
        child: Text('页面不存在: ${state.location}'),
      ),
    ),
  ),
  // ...其他配置
);

8、 完整项目结构示例

bash 复制代码
lib/
├── main.dart
├── routes/
│   ├── app_routes.dart      # 路由路径常量
│   ├── router_config.dart   # 路由配置入口
│   ├── home_route.dart      # 首页路由配置
│   ├── details_route.dart   # 详情页路由配置
│   └── route_guard.dart     # 路由守卫
├── models/
│   └── route_args.dart      # 路由参数模型
├── utils/
│   └── navigation_service.dart # 导航服务
└── pages/
    ├── home_page.dart
    └── details_page.dart
相关推荐
调皮的芋头29 分钟前
iOS各个证书生成细节
人工智能·ios·app·aigc
太空漫步112 小时前
android社畜模拟器
android
神秘_博士2 小时前
自制AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有iPhone
arm开发·python·物联网·flutter·docker·gitee
coooliang2 小时前
【iOS】SwiftUI状态管理
ios·swiftui·swift
陈皮话梅糖@4 小时前
Flutter 网络请求与数据处理:从基础到单例封装
flutter·网络请求
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android