{"id":26334402,"url":"https://github.com/yanglang116/flutter_mixin_router","last_synced_at":"2025-03-16T00:18:48.775Z","repository":{"id":56829691,"uuid":"470421334","full_name":"YangLang116/flutter_mixin_router","owner":"YangLang116","description":"A flutter package for router","archived":false,"fork":false,"pushed_at":"2023-08-09T15:11:26.000Z","size":14,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2023-08-20T21:59:55.755Z","etag":null,"topics":["android","arouter","flutter","ios","router"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/flutter_mixin_router","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/YangLang116.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-16T03:41:56.000Z","updated_at":"2023-03-20T09:55:45.000Z","dependencies_parsed_at":"2023-02-08T17:45:23.921Z","dependency_job_id":null,"html_url":"https://github.com/YangLang116/flutter_mixin_router","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YangLang116%2Fflutter_mixin_router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YangLang116%2Fflutter_mixin_router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YangLang116%2Fflutter_mixin_router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/YangLang116%2Fflutter_mixin_router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/YangLang116","download_url":"https://codeload.github.com/YangLang116/flutter_mixin_router/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243806561,"owners_count":20350864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","arouter","flutter","ios","router"],"created_at":"2025-03-16T00:18:48.239Z","updated_at":"2025-03-16T00:18:48.763Z","avatar_url":"https://github.com/YangLang116.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- \n使用Dart mixin机制实现PageRoute模块化\n--\u003e\n\n## 1、项目背景\n\n几乎所有的Flutter应用都是采用路由表的方式对路由进行管理，即在应用初始化时，提前把**路由名称**和对应的页面注册到路由表中，应用内部通过**路由名称**跳转到相应的页面。以如下Demo项目为例：\n\n### 1.1、项目目录结构\n\n![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cc95703c255c4560b22549f5a9766754~tplv-k3u1fbpfcp-watermark.image?)\n\n整个项目包含三个页面(大厅页面、设置页面A、设置页面B) 以及 一个入口文件(main.dart)\n\n### 1.2、项目代码说明\n\nA、B 设置页面（屏幕正中间展示当前页面名称）\n\n```\nclass APage extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: Text('APage'),\n    );\n  }\n}\n\n\nclass BPage extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: Text('BPage'),\n    );\n  }\n}\n```\n\n大厅页面（屏幕正中间展示页面名称，点击名称跳转到页面A）\n\n```\nclass HomePage extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: GestureDetector(\n        onTap: () =\u003e Navigator.pushNamed(context, '/setting_a'),  //路由跳转\n        child: Text('HomePage'),\n      ),\n    );\n  }\n}\n```\n\n入口文件（注册应用路由表）\n\n```\nclass MyApp extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      ...\n      initialRoute: '/home',\n      routes: {\n        '/home': (context) =\u003e HomePage(),    //路由注册\n        '/setting_a': (context) =\u003e APage(),\n        '/setting_b': (context) =\u003e BPage(),\n      },\n    );\n  }\n}\n```\n\n## 2、项目问题\n\n随着项目的不断开发迭代，会有越来越多的页面被添加到应用中。由于新增加的页面都需要提前注册到路由表中，此时入口文件(main.dart）会变得越来越臃肿：\n\n```\nroutes: {\n        '/home': (context) =\u003e HomePage(),\n        '/setting_a': (context) =\u003e APage(),\n        '/setting_b': (context) =\u003e BPage(),\n        ...\n        ...\n},\n```\n\n不容小觑的还有另外一个问题，项目正在变得越来越**扁平化！！！** ，毕竟项目路由并没有分结构进行管理：\n\n![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff7f37326cc44a06aa0ef85cac346a84~tplv-k3u1fbpfcp-watermark.image?)\n\n## 3、解决方案\n\n### 3.1、路由注册方案改造\n\n![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2e334ef56d44f72851d0a10889545f1~tplv-k3u1fbpfcp-watermark.image?)\n\n在项目的入口文件中，只需要添加对应的业务模块，而页面的注册过程就交给对应的模块完成，保证项目结构化的同时，也极大避免了入口文件臃肿问题。\n\n### 3.2、模块管理基类创建\n\n```\nclass MixinRouterContainer {\n  ///init router\n  Map\u003cString, WidgetBuilder\u003e installRouters() =\u003e {};\n\n  ///open page\n  Future\u003cT?\u003e? openPage\u003cT\u003e(BuildContext context, String pageName, ... Map\u003cdynamic, dynamic\u003e? arguments,...}) {\n    Map\u003cString, dynamic\u003e args = {'args': arguments};\n    switch (pushType) {\n      case RoutePushType.pushNamed:\n        return Navigator.pushNamed(context, pageName, arguments: args);\n      ...\n    }\n  }\n}\n```\n\n整个基类的核心包括**两个**方法：\n-   **installRouters**:  配置属于该模块的路由表\n-   **openPage**: 打开相应的路由页面\n\n### 3.3、模块页面注册：\n\n```\nmixin HomeRouteContainer on MixinRouterContainer {\n  @override\n  Map\u003cString, WidgetBuilder\u003e installRouters() {\n    Map\u003cString, WidgetBuilder\u003e originRoutes = super.installRouters();\n    Map\u003cString, WidgetBuilder\u003e newRoutes = {};\n    newRoutes['/home'] = (context) =\u003e HomePage(); //注册大厅页面\n    newRoutes.addAll(originRoutes);\n    return newRoutes;\n  }\n}\n\nmixin SettingRouteContainer on MixinRouterContainer {\n  @override\n  Map\u003cString, WidgetBuilder\u003e installRouters() {\n    Map\u003cString, WidgetBuilder\u003e originRoutes = super.installRouters();\n    Map\u003cString, WidgetBuilder\u003e newRoutes = {};\n    newRoutes['/setting_a'] = (context) =\u003e APage();  //注册A页面  \n    newRoutes['/setting_b'] = (context) =\u003e BPage();  //注册B页面\n    newRoutes.addAll(originRoutes);\n    return newRoutes;\n  }\n}\n```\n\n可以看到HomeRouteContainer 把 HomePage添加到自己的路由表中，同样SettingRouteContainer管理了SettingA、SettingB两个页面。\n\n### 3.4、App模块注册：\n\n```\nclass AppRouteContainer extends MixinRouterContainer\n    with HomeRouteContainer, SettingRouteContainer {  //通过mixin机制粘合项目各个路由模块\n  AppRouteContainer._();\n\n  static AppRouteContainer _instance = AppRouteContainer._();\n\n  static AppRouteContainer get share =\u003e _instance;\n}\n\n\nclass MyApp extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      ...\n      initialRoute: '/home',\n      routes: AppRouteContainer.share.installRouters(),  //注册总路由表\n    );\n  }\n}\n```\n\n要注意的是，需要创建一个新的类，来粘合项目所有的路由模块，如上面的AppRouteContainer所示，声明成一个单例，方便在项目中使用：\n\n-   注册项目路由表：AppRouteContainer.share.installRouters()\n-   页面跳转：AppRouteContainer.share.openPage(context, '/setting_a')\n\n\n## 4、方案延伸\n\n### 情况一：\n\n说明： 在项目开发过程中，除了简单的页面跳转外，还存在路由拦截。比如：用户在没登录的情况下，想打开个人主页，那么就需要拦截这一过程，并跳转到登录页面。\n\n解决方案：只需在原有的路由管理模块的基类( MixinRouterContainer )上，做进一步的封装。通过添加拦截路由表，并重写路由跳转过程：\n\n```\ntypedef MixinRouteInterceptor = bool Function(BuildContext context, String pageName, ...);\n\nclass MixinRouterInterceptContainer extends MixinRouterContainer {\n  \n  final Map\u003cString, MixinRouteInterceptor\u003e _routeInterceptorTable = {};\n\n  void registerRouteInterceptor(String pageName, MixinRouteInterceptor interceptor) {\n    _routeInterceptorTable[pageName] = interceptor;\n  }\n\n  void unRegisterRouteInterceptor(String pageName) {\n    _routeInterceptorTable.remove(pageName);\n  }\n\n  @override\n  Future\u003cT?\u003e? openPage\u003cT\u003e(BuildContext context, String pageName,...) {\n    if (!_routeInterceptorTable.containsKey(pageName)) {\n      return super.openPage(context,pageName,...);\n    }\n    MixinRouteInterceptor interceptor = _routeInterceptorTable[pageName]!;\n    bool needIntercept = interceptor.call(context,pageName,...);\n    if (needIntercept) {\n      return Future.value(null);\n    } else {\n      return super.openPage(context,pageName,...);\n    }\n  }\n}\n```\n\n例如：在打开大厅页面之前，判断用户是否登录，如未登录则跳转到登录页面。\n\n```\nmixin HomeRouteContainer on MixinRouterInterceptContainer {\n  @override\n  Map\u003cString, WidgetBuilder\u003e installRouters() {\n    registerRouteInterceptor('/home', (...) =\u003e if(!isLogin) openLoginPage());  //注册拦截路由表\n    Map\u003cString, WidgetBuilder\u003e originRoutes = super.installRouters();\n    Map\u003cString, WidgetBuilder\u003e newRoutes = {};\n    newRoutes['/home'] = (context) =\u003e HomePage();\n    newRoutes.addAll(originRoutes);\n    return newRoutes;\n  }\n}\n```\n\n### 情况二：\n\n说明：为了通过外链能打开对应的页面，很多项目都是Url统跳。\n\n解决方案：只需要对原有的 AppRouteContainer 进行扩展，代理默认的页面打开方法，实现url解析：\n\n```\nclass AppRouteContainer extends MixinRouterContainer\n    with HomeRouteContainer, SettingRouteContainer {  //通过mixin机制粘合项目各个路由模块\n    \n   Future\u003cT?\u003e? urlToPage\u003cT\u003e(BuildContext context, String urlStr, ...) {\n  \tUri? url = Uri.tryParse(urlStr);\n  \tif (url == null) return Future.error('parse url fail');\n  \tMap\u003cString, String\u003e args = {};\n    args.addAll(url.queryParameters);\n    args['_url'] = urlStr;\n    String pageName = url.host;\n    super.openPage(context,'/' + pageName ...);\n  }\n}\n```\n\n在进行url统跳时，调用urlToPage即可打开相应的flutter页面。\n\n## 5、深入探索\n\n对项目的路由改造到此就结束了么？回过头来再想想，发现还是存在一些问题：\n\n-   需要手动创建并维护不同的路由管理模块(HomeRouteContainer、SettingRouteContainer）\n-   新的页面都需要在对应的模块类中进行手动注册\n\n客户端原生项目对于这类问题，可以通过注解的方式解决，类似阿里的ARouter，那么Flutter也可以借鉴此方式完成进一步的优化，利用注解去生成对应的路由模块管理文件，避免手动维护，具体如下：\n\n### 5.1、注解子路由表\n\n```\nconst String HOME_ROUTE_TABLE = 'HomeRouteTable';\nconst String SETTING_ROUTE_TABLE = 'SettingsRouteTable';\n\n//tDescription: 仅仅作为生成类的注释\n@RouterTableList(\n  tableList: [\n    RouterTable(tName: HOME_ROUTE_TABLE, tDescription: '大厅路由模块'),\n    RouterTable(tName: SETTING_ROUTE_TABLE, tDescription: '设置路由模块'),\n  ],\n)\n\n\n//with HomeRouterTable, MineRouterTable，即上面声明的两个路由表的名字\nclass AppRouteContainer extends MixinRouterInterceptContainer\n    with HomeRouteTable, SettingsRouteTable {\n  AppRouteContainer._();\n\n  static AppRouteContainer _instance = AppRouteContainer._();\n\n  static AppRouteContainer get share =\u003e _instance;\n}\n```\n\n### 5.2、注解普通路由\n\n```\n//方式一：不使用路由参数\n@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_a')\nclass APage extends StatelessWidget {\n  const APage({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: Text('APage'),\n    );\n  }\n}\n\n//方式二：使用路由参数\n@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_a', arg=true)\nclass APage extends StatelessWidget {\n  const APage(dynamic args, {Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: Text('APage'),\n    );\n  }\n}\n```\n\n### 5.3、注解拦截路由\n\n```\n@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_b')\nclass BPage extends StatelessWidget {\n  const BPage({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: Text('BPage'),\n    );\n  }\n}\n\n@MixinInterceptRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_b')\nbool interceptorMinePage(context, pageName, pushType, {arguments, predicate}) { //函数签名固定写法\n  print('toLogin');\n  return true;\n}\n```\n\n## 6、集成使用\n\n在项目的pubspec.yaml中添加依赖，即可开启注解路由之旅\n\n```\ndependencies:\n  flutter:\n    sdk: flutter\n  flutter_mixin_router: ^1.0.0      # 添加路由模块管理基类\n  flutter_mixin_router_ann: 1.0.0   # 添加注解类\n\ndev_dependencies:\n  build_runner: 2.1.8               # 添加依赖\n  flutter_mixin_router_gen: 1.0.1   # 添加代码生成工具库\n```\n\n在项目页面上添加对应的注解后，执行以下命令生成对应的路由代码\n```\n# 清除增量编译缓存\nflutter packages pub run build_runner clean\n\n# 重新生成代码\nflutter packages pub run build_runner build --delete-conflicting-outputs\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyanglang116%2Fflutter_mixin_router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyanglang116%2Fflutter_mixin_router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyanglang116%2Fflutter_mixin_router/lists"}