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
相关推荐
张风捷特烈13 小时前
Flutter 伪3D绘制#03 | 轴测投影原理分析
android·flutter·canvas
得物技术13 小时前
得物 iOS 启动优化之 Building Closure
ios·性能优化
马拉萨的春天17 小时前
flutter 项目结构目录以及pubspec.ymal等文件描述
flutter
omegayy17 小时前
Unity 2022.3.x部分Android设备播放视频黑屏问题
android·unity·视频播放·黑屏
mingqian_chu17 小时前
ubuntu中使用安卓模拟器
android·linux·ubuntu
自动花钱机17 小时前
Kotlin问题汇总
android·开发语言·kotlin
行墨19 小时前
Kotlin 主构造函数
android
前行的小黑炭19 小时前
Android从传统的XML转到Compose的变化:mutableStateOf、MutableStateFlow;有的使用by有的使用by remember
android·kotlin
_一条咸鱼_20 小时前
Android Compose 框架尺寸与密度深入剖析(五十五)
android
在狂风暴雨中奔跑20 小时前
使用AI开发Android界面
android·人工智能