Flutter路由管理插件owlet_router的使用

Flutter路由管理插件owlet_router的使用

Owlet Router

owlets_on_the_tree

版本 喜欢 问题 许可证

owlet_router 是一个路由管理器。它利用路由构建器来构建路由。

它的设计目的包括:

  • 提供一个清晰且易于定义的路由管理器,便于阅读和使用。
  • 基于基础的Flutter路由构建,允许集成各种页面路由类型,并能够自定义和扩展路由。
  • 支持模块化路由,使路由可以分段并创建独立的路由。
  • 提供检查、阻止或重定向路由的能力,使其在推送之前进行处理。

目录

开始使用

dependencies 中添加以下依赖项:

dependencies:
  owlet_router: '0.0.2'

导入包:

import 'package:owlet_router/router.dart';

用法

创建顶级路由(根路由)的 AppRoute 类。此路由有两个直接子路由 splashhome

class AppRoute extends RouteBase {
  AppRoute() : super.root(); // 默认设置根段为'/'

  final splash = MaterialRouteBuilder(
      '/', pageBuilder: (context, settings) => const SplashPage());

  final home = MaterialRouteBuilder(
      '/home', pageBuilder: (context, settings) => const HomePage());

  final profiles = ProfileRoute('/profile');

  List<RouteBase> get children => [splash, home, profiles];
}

ProfileRouteAppRoute 的子路由模块。它有两个子路由,详情页 (/profile/) 和更新个人资料页 (/profile/update-profile)。

class ProfileRoute extends RouteBase {
  ProfileRoute(super.segment);

  final details = MaterialRouteBuilder(
      '/', pageBuilder: (context, settings) => const DetailsPage());

  final update = MaterialRouteBuilder(
      '/update-profile', pageBuilder: (context, settings) => const UpdateProfilePage());

  List<RouteBase> get children => [details, update];
}

要使用此路由,必须将顶级路由(AppRoute)注入到您的导航器中使用 NavigationService

final appRoute = AppRoute(); // 根路由

final service = NavigationService(
    navigationKey: GlobalKey(),
    routeObservers: [ /* your observers */ ],
    initialRoute: '/',
    root: appRoute,
    unknownRoute: owletDefaultUnknownRoute);

// ...
// 在根导航器中使用

Widget build(BuildContext context) {
  return MaterialApp.router(
    routerConfig: service.routerConfig,

    /// ...
  );
}

// *************************************************
// 在嵌套导航器中使用                       

Widget build(BuildContext context) {
  return OwletNavigator(service);
}

要推送 profile 路由,您可以使用导航器的常规 pushNamed 方法:

final result = await Navigator.of(context).pushNamed('/profile/');

或者从路由字段调用:

final result = await appRoute.profiles.pushNamed(
                            args: //..., 
                            params: // ... 查询参数
                            fragment: // #route-fragment,
                        );

功能

1. NavigationService

NavigationService 提供了新的 Navigator 所需的方法:

Navigator(
    key: service.navigationKey,
    initialRoute: service.initialRoute,
    observers: <NavigatorObserver>[service.history, ...service.routeObservers],
    onGenerateRoute: service.onGenerateRoute,
    onPopPage: service.onPopPage,
    onUnknownRoute: service.onUnknownRoute,
  );
1.1. Owlet Navigator

OwletNavigator 是一个自定义实现的 Navigator,旨在提供诸如路由守卫和命名函数等高级功能。默认情况下,RouterDelegate 使用 OwletNavigator

Widget build(BuildContext context) {
  return OwletNavigator(service);
}
2. Provider

为了优化性能,可以在 Navigator 中将 NavigationService 设置为单例。这可以通过创建单例变量或使用依赖注入(如 GetIt)来实现。

NavigationServiceProvider 允许您在上下文中访问 NavigationService

final service = NavigationService.of(context);

此外,将路由作为静态字段创建可以提高性能。但是,请注意,静态路由不能动态更改其父路由。没有必要将每个路由都设置为静态字段,模块管理器功能仍然可以与单独的路由一起工作,允许您为应用程序的每个部分创建代表路由。

要在当前上下文中查找对应的 RouteBase 对象,请遍历路由树。

final route = RouteBase.of<ROUTE_TYPE>(context);

但是,这种方法可能会导致最坏情况下的时间复杂度为 O(n * k),其中’n’表示路由树的平均深度,'k’表示 NavigationService 层的数量。前面提到的另一种方法是从部分路由直接确定路由类型。

final route = profiles.findType<ROUTE_TYPE>();
3. 路由历史

NavigationService 提供了一个记录当前路由的路由历史观察者。

void listenHistory() {
  final history = service.history;
  history.addListener(() {
    /// 监听路由变化
    if (kDebugMode) {
      print(history.current);
    }
  });
  final currentRoute = history.current; // 获取当前显示的路由

  bool isAppeared = history.contains('/home'); // 检查 /home 路由是否显示

  // 查找符合条件的路由,如果需要对其执行某些操作(例如使用 Navigator.replacement)
  final route = history.nearest(/* condition */);
}
4. 未知路由

如果找不到路由或在构造过程中发生错误,将使用未知路由作为替代。

final service = NavigationService(
  // ...
    unknownRoute: yourRoute
);
5. 路由

owlet_router 利用模块架构进行路由。要定义路由,请创建一个继承 RouteBase 的类。在此类内部,您可以指定该路由的子路由。重复此过程以扩展路由树。

final appRoute = AppRoute();

// ..

class AppRoute extends RouteBase {
  AppRoute() : super.root(); // 如果此路由是根路由

  final splash = MaterialRouteBuilder(
      '/', pageBuilder: (context, settings) => const SplashPage());

  final home = MaterialRouteBuilder(
      '/home', pageBuilder: (context, settings) => const HomePage());

  final items = ListItemRoute('/item');

  [@override](/user/override)
  List<RouteBase> get children => [splash, home, items];
}

class ListItemRoute extends RouteBase {
  ListItemRoute(super.segment);

  final list = MaterialRouteBuilder(
      '/list', pageBuilder: (context, settings) => const ListItemPage());

  late final detail = RouteGuard(
    routeBuilder: RouteBuilder<String, dynamic>(
      '/detail',
      builder: (settings) {
        if (settings.arguments is String) {
          return MaterialPageRoute(
            settings: settings,
            builder: (context) => DetailPage(item: settings.arguments as String),
          );
        }
        return CancelledRoute(false);
      },
    ),
    routeGuard: (context, route) async {
      if (!navigatorServices.history.contains(list.path)) {
        Navigator.push(context, list.noAnimationBuilder()!);
      }
      return route;
    },
  );

  [@override](/user/override)
  List<RouteBase> get children => [detail, list];
}

警告

不要忘记将您的路由添加到 List<RouteBase> get children 获取器中。如果没有注册,该路由将无法找到。

5.1. 路由构建器

RouteBase 视为文件夹;它不创建任何东西,因此是一个不可启动的路由。要创建页面路由,我们使用 RouteBuilder,这是一个可启动的路由。

final page1 = RouteBuilder(
  '/page1',
  builder: (settings) =>
      MaterialPageRoute(
        settings: settings,
        builder: (context) => Page1(arguments: settings.arguments),
      ),
);

RouteBuilder 允许您自定义 PageRoute,并且我们已经创建了一些自定义的 RouteBuilder。您还可以通过覆盖 RouteBuilder.builder 方法来自定义 PageRoute

5.2. 如何工作?
  1. 路由路径是什么? 要获取 splash 的路径,可以使用 splash.path 方法。splash 的路径是由其父路径与其自身路径连接生成的。如果结果包含重复的斜杠 (//),它们将合并为一个。因此,如果您希望段具有与父路由相同的路径,只需将其段设置为单个斜杠 (/)。

  2. 推送新路由 如果知道路径,一种简化的方法是使用 Navigator.pushNamed 将其推送到导航器。

    Navigator.of(context).pushNamed('/home');
    
    // 或者
    Navigator.of(context).pushNamed(appRoute.home.path);
    

    RouteBuilder 的扩展提供了另一种方法来推送它。

    appRoute.home.pushNamed(context);
    
  3. 参数和结果 RouteBuilder<A, T>.pushNamed 方法提供了有关路由的参数类型和结果类型的信息。

    final page2 = RouteBuilder<String, bool>(
      '/page2',
      builder: (settings) {
        final greeting = settings.arguments as String?;
        return MaterialPageRoute(
          builder: (context) => TestApp(content: greeting),
        );
      },
    );
    
    // ...
    final bool result = await page2.pushName(context, args: /* 字符串类型是必需的*/);
    
  4. 在模块中 如果 ListItemRoute 位于模块包中,则无法访问 appRoute,因为它是在主包中定义的。相反,您可以使用 RouteBase.of<ListItemRoute>() 来特定地检索您的 items 路由。这种方法促进了不同包之间代码的更大独立性。

5.3. 自定义路由构建器
  • MaterialRouteBuilder: 它会创建一个 MaterialPageRoute

  • CupertinoRouteBuilder: 它会创建一个 CupertinoPageRoute

  • NoTransitionRouteBuilder: 它会创建一个没有过渡效果的 PageRoute

    final splash = MaterialRouteBuilder('/', pageBuilder: (context, settings) => const SplashPage());
    
  • RouteGuard:

    • 它仅与 OwletNavigator 一起工作。RouteGuard 提供了一个在推送路由前调用的 routeGuard 方法,允许您在推送前修改路由。
    • 为了防止路由被推送,返回一个 CancelledRoute。要重定向它,返回另一个路由或在 routeGuard 方法中使用 RedirectRoute('redirect-path')
    final detail = RouteGuard(
      route: RouteBuilder<String, dynamic>(
        '/detail',
        builder: (settings) {
          if (settings.arguments is String) {
            return MaterialPageRoute(
                settings: settings,
                builder: (context) => DetailPage(item: settings.arguments as String));
          }
          return CancelledRoute(false);
        },
      ),
      routeGuard: (context, route) async {
        if (!navigatorServices.history.contains(list.path)) {
          Navigator.push(context, list.noAnimationBuilder()!);
        }
        return route;
      },
    );
    

注意

RouteGuard 仅适用于以下函数:

  • Navigator.push
  • Navigator.pushNamed
  • Navigator.popAndPushNamed
  • Navigator.pushReplacement
  • Navigator.pushReplacementNamed
  • Navigator.pushAndRemoveUntil
  • Navigator.pushNamedAndRemoveUntil
  • NamedFunctionRoute: 一种理想的方法是命名一个函数并通过导航器调用它。当推送 NamedFunctionRoute 时,不会添加路由;而是调用定义的函数,其结果将被返回。

    类似于 RouteGuard,它也仅与 OwletNavigator 一起工作。

    final action = NamedFunctionRouteBuilder(
      '/action',
      callback: (context, route) => print('Hello World'),
    );
    

注意

NamedFunctionRouteBuilder 仅适用于以下函数:

  • Navigator.pushNamed
  • Navigator.popAndPushNamed
5.4. 排查问题
  1. 当使用 appRoute.profiles.update.path 方法获取路由时,预期路径为 /home/profiles/update,但结果为 /update,请确保已在 profile 路由的 children 获取器中包含此路由。
  2. 当更新路由的子路由时遇到问题 1,即使它已经在 children 列表中存在,也要确保在更改路由后调用了 repair 方法。
  3. 为了获得最佳性能,路由器应该更稳定,更改次数更少。因此,如果您在更改路由并执行热加载时遇到问题,请考虑使用 ‘热重启’ 方法来解决此问题。虽然 repair 方法可能有效,但它会消耗额外资源,因此建议在发布模式下避免使用它。

嵌套导航器

要使用嵌套导航器。创建一个带有 OwletNavigatorNestedPage

static final nestedService = NavigatorService( /*...*/ );

class NestedPage extends StatefulWidget {
  /// ...
}

class NestedPageState extends State<NestedPage> {
  /// ...
  Widget build(BuildContext context) {
    return OwletNavigator(nestedService);
  }
}

在根路由中创建一个具有 NestedService 的字段:

class AppRoute extends RouteBase {
  // ...
  final nestedPage = NestedService(
      service: nestedService,
      route: MaterialRouteBuilder('/nested', pageBuilder: (context, settings) => const NestedPage()));
}

每当您推送一个路由作为 /nested/sub-path/**,如果 NestedPage 已经存在于导航器中,nestedService 将被推送到路径 /sub-path/**。否则,一个新的 NestedPage 将被推送到导航器。

Navigator.of(context, rootNavigator: true).pushNamed('/nested/sub-path/**');

嵌套路由

类似于嵌套导航器,嵌套路由有助于将路由分为两个组件。实际路由被注入到导航服务中,而剩余的段作为页面小部件的参数。

class ItemDetailPageState extends State<ItemDetailPage> {
  late final NestedRoute nestedRoute;

  [@override](/user/override)
  void initState() {
    super.initState();
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
      nestedRoute = RouteBase.of<NestedRoute>(context);

      nestedRoute.addListener(onRouteNotifier);
    });
  }

  [@override](/user/override)
  void dispose() {
    nestedRoute.removeListener(onRouteNotifier);
    super.dispose();
  }

  void onRouteNotifier() {
    print(nestedRoute.value as RouteSettings?);
  }
}

//....

class AppRoute extends RouteBase {
  // ...
  final itemDetail = NestedRoute(
      route: MaterialRouteBuilder('/item-detail', pageBuilder: (context, settings) => const ItemDetailPage()));
}

更多关于Flutter路由管理插件owlet_router的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter路由管理插件owlet_router的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


owlet_router 是一个用于 Flutter 应用的路由管理插件,它提供了一种简单且灵活的方式来管理应用程序中的页面导航。以下是如何使用 owlet_router 插件的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 owlet_router 的依赖:

dependencies:
  flutter:
    sdk: flutter
  owlet_router: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 初始化路由

main.dart 文件中初始化 owlet_router

import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Owlet Router Example',
      // 使用 OwletRouter 来管理路由
      onGenerateRoute: OwletRouter.onGenerateRoute,
      initialRoute: '/',
    );
  }
}

3. 定义路由

owlet_router 中,你可以通过 OwletRouter 类来定义路由。你可以在应用的任何地方定义路由,但通常会在 main.dart 或一个单独的路由配置文件中定义。

import 'package:owlet_router/owlet_router.dart';

class Routes {
  static const String home = '/';
  static const String about = '/about';
  static const String profile = '/profile';
}

void configureRoutes() {
  OwletRouter.configure([
    OwletRoute(
      path: Routes.home,
      builder: (context, params) => HomePage(),
    ),
    OwletRoute(
      path: Routes.about,
      builder: (context, params) => AboutPage(),
    ),
    OwletRoute(
      path: Routes.profile,
      builder: (context, params) => ProfilePage(),
    ),
  ]);
}

main.dart 中调用 configureRoutes

void main() {
  configureRoutes();
  runApp(MyApp());
}

4. 页面导航

你可以使用 OwletRouter 提供的方法进行页面导航。

import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';

class HomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                // 导航到 About 页面
                OwletRouter.pushNamed(context, Routes.about);
              },
              child: Text('Go to About'),
            ),
            ElevatedButton(
              onPressed: () {
                // 导航到 Profile 页面
                OwletRouter.pushNamed(context, Routes.profile);
              },
              child: Text('Go to Profile'),
            ),
          ],
        ),
      ),
    );
  }
}

5. 传递参数

你可以在导航时传递参数,并在目标页面中获取这些参数。

OwletRouter.pushNamed(context, Routes.profile, arguments: {'userId': 123});

// 在 ProfilePage 中获取参数
class ProfilePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final Map<String, dynamic> args = OwletRouter.of(context).arguments;
    final int userId = args['userId'];
    return Scaffold(
      appBar: AppBar(
        title: Text('Profile'),
      ),
      body: Center(
        child: Text('User ID: $userId'),
      ),
    );
  }
}

6. 其他功能

owlet_router 还提供了其他一些功能,例如:

  • 路由拦截:你可以在路由跳转前进行拦截,例如检查用户是否登录。
  • 嵌套路由:支持嵌套路由,可以用于复杂的页面结构。

7. 示例代码

以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:owlet_router/owlet_router.dart';

void main() {
  configureRoutes();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Owlet Router Example',
      onGenerateRoute: OwletRouter.onGenerateRoute,
      initialRoute: Routes.home,
    );
  }
}

class Routes {
  static const String home = '/';
  static const String about = '/about';
  static const String profile = '/profile';
}

void configureRoutes() {
  OwletRouter.configure([
    OwletRoute(
      path: Routes.home,
      builder: (context, params) => HomePage(),
    ),
    OwletRoute(
      path: Routes.about,
      builder: (context, params) => AboutPage(),
    ),
    OwletRoute(
      path: Routes.profile,
      builder: (context, params) => ProfilePage(),
    ),
  ]);
}

class HomePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                OwletRouter.pushNamed(context, Routes.about);
              },
              child: Text('Go to About'),
            ),
            ElevatedButton(
              onPressed: () {
                OwletRouter.pushNamed(context, Routes.profile, arguments: {'userId': 123});
              },
              child: Text('Go to Profile'),
            ),
          ],
        ),
      ),
    );
  }
}

class AboutPage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('About'),
      ),
      body: Center(
        child: Text('About Page'),
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final Map<String, dynamic> args = OwletRouter.of(context).arguments;
    final int userId = args['userId'];
    return Scaffold(
      appBar: AppBar(
        title: Text('Profile'),
      ),
      body: Center(
        child: Text('User ID: $userId'),
      ),
    );
  }
}
回到顶部