Flutter路由管理插件flutter_mixin_router的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter路由管理插件flutter_mixin_router的使用

几乎所有的Flutter应用都是采用路由表的方式对路由进行管理,即在应用初始化时,提前把路由名称和对应的页面注册到路由表中,应用内部通过路由名称跳转到相应的页面。以如下Demo项目为例:

1.1、项目目录结构

image.png

整个项目包含三个页面(大厅页面、设置页面A、设置页面B) 以及 一个入口文件(main.dart)

1.2、项目代码说明

A、B 设置页面(屏幕正中间展示当前页面名称)

class APage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('APage'),
    );
  }
}

class BPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('BPage'),
    );
  }
}

大厅页面(屏幕正中间展示页面名称,点击名称跳转到页面A)

class HomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () => Navigator.pushNamed(context, '/setting_a'),  //路由跳转
        child: Text('HomePage'),
      ),
    );
  }
}

入口文件(注册应用路由表)

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      initialRoute: '/home',
      routes: {
        '/home': (context) => HomePage(),    //路由注册
        '/setting_a': (context) => APage(),
        '/setting_b': (context) => BPage(),
      },
    );
  }
}

2、项目问题

随着项目的不断开发迭代,会有越来越多的页面被添加到应用中。由于新增加的页面都需要提前注册到路由表中,此时入口文件(main.dart)会变得越来越臃肿:

routes: {
        '/home': (context) => HomePage(),
        '/setting_a': (context) => APage(),
        '/setting_b': (context) => BPage(),
        ...
        ...
},

不容小觑的还有另外一个问题,项目正在变得越来越扁平化!!! ,毕竟项目路由并没有分结构进行管理:

image.png

3、解决方案

3.1、路由注册方案改造

image.png

在项目的入口文件中,只需要添加对应的业务模块,而页面的注册过程就交给对应的模块完成,保证项目结构化的同时,也极大避免了入口文件臃肿问题。

3.2、模块管理基类创建
class MixinRouterContainer {
  ///init router
  Map<String, WidgetBuilder> installRouters() => {};

  ///open page
  Future<T?>? openPage<T>(BuildContext context, String pageName, ... Map<dynamic, dynamic>? arguments,...}) {
    Map<String, dynamic> args = {'args': arguments};
    switch (pushType) {
      case RoutePushType.pushNamed:
        return Navigator.pushNamed(context, pageName, arguments: args);
      ...
    }
  }
}

整个基类的核心包括两个方法:

  • installRouters: 配置属于该模块的路由表
  • openPage: 打开相应的路由页面
3.3、模块页面注册:
mixin HomeRouteContainer on MixinRouterContainer {
  @override
  Map<String, WidgetBuilder> installRouters() {
    Map<String, WidgetBuilder> originRoutes = super.installRouters();
    Map<String, WidgetBuilder> newRoutes = {};
    newRoutes['/home'] = (context) => HomePage(); //注册大厅页面
    newRoutes.addAll(originRoutes);
    return newRoutes;
  }
}

mixin SettingRouteContainer on MixinRouterContainer {
  @override
  Map<String, WidgetBuilder> installRouters() {
    Map<String, WidgetBuilder> originRoutes = super.installRouters();
    Map<String, WidgetBuilder> newRoutes = {};
    newRoutes['/setting_a'] = (context) => APage();  //注册A页面  
    newRoutes['/setting_b'] = (context) => BPage();  //注册B页面
    newRoutes.addAll(originRoutes);
    return newRoutes;
  }
}

可以看到HomeRouteContainerHomePage 添加到自己的路由表中,同样 SettingRouteContainer 管理了 SettingASettingB 两个页面。

3.4、App模块注册:
class AppRouteContainer extends MixinRouterContainer
    with HomeRouteContainer, SettingRouteContainer {  //通过mixin机制粘合项目各个路由模块
  AppRouteContainer._();

  static AppRouteContainer _instance = AppRouteContainer._();

  static AppRouteContainer get share => _instance;
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      initialRoute: '/home',
      routes: AppRouteContainer.share.installRouters(),  //注册总路由表
    );
  }
}

要注意的是,需要创建一个新的类,来粘合项目所有的路由模块,如上面的 AppRouteContainer 所示,声明成一个单例,方便在项目中使用:

  • 注册项目路由表:AppRouteContainer.share.installRouters()
  • 页面跳转:AppRouteContainer.share.openPage(context, '/setting_a')

4、方案延伸

情况一:

说明: 在项目开发过程中,除了简单的页面跳转外,还存在路由拦截。比如:用户在没登录的情况下,想打开个人主页,那么就需要拦截这一过程,并跳转到登录页面。

解决方案:只需在原有的路由管理模块的基类(MixinRouterContainer)上,做进一步的封装。通过添加拦截路由表,并重写路由跳转过程:

typedef MixinRouteInterceptor = bool Function(BuildContext context, String pageName, ...);

class MixinRouterInterceptContainer extends MixinRouterContainer {
  
  final Map<String, MixinRouteInterceptor> _routeInterceptorTable = {};

  void registerRouteInterceptor(String pageName, MixinRouteInterceptor interceptor) {
    _routeInterceptorTable[pageName] = interceptor;
  }

  void unRegisterRouteInterceptor(String pageName) {
    _routeInterceptorTable.remove(pageName);
  }

  @override
  Future<T?>? openPage<T>(BuildContext context, String pageName,...) {
    if (!_routeInterceptorTable.containsKey(pageName)) {
      return super.openPage(context,pageName,...);
    }
    MixinRouteInterceptor interceptor = _routeInterceptorTable[pageName]!;
    bool needIntercept = interceptor.call(context,pageName,...);
    if (needIntercept) {
      return Future.value(null);
    } else {
      return super.openPage(context,pageName,...);
    }
  }
}

例如:在打开大厅页面之前,判断用户是否登录,如未登录则跳转到登录页面。

mixin HomeRouteContainer on MixinRouterInterceptContainer {
  @override
  Map<String, WidgetBuilder> installRouters() {
    registerRouteInterceptor('/home', (...) => if(!isLogin) openLoginPage());  //注册拦截路由表
    Map<String, WidgetBuilder> originRoutes = super.installRouters();
    Map<String, WidgetBuilder> newRoutes = {};
    newRoutes['/home'] = (context) => HomePage();
    newRoutes.addAll(originRoutes);
    return newRoutes;
  }
}
情况二:

说明:为了通过外链能打开对应的页面,很多项目都是Url统跳。

解决方案:只需要对原有的 AppRouteContainer 进行扩展,代理默认的页面打开方法,实现url解析:

class AppRouteContainer extends MixinRouterContainer
    with HomeRouteContainer, SettingRouteContainer {  //通过mixin机制粘合项目各个路由模块
    
   Future<T?>? urlToPage<T>(BuildContext context, String urlStr, ...) {
  	Uri? url = Uri.tryParse(urlStr);
  	if (url == null) return Future.error('parse url fail');
  	Map<String, String> args = {};
    args.addAll(url.queryParameters);
    args['_url'] = urlStr;
    String pageName = url.host;
    super.openPage(context,'/' + pageName ...);
  }
}

在进行url统跳时,调用urlToPage即可打开相应的flutter页面。

5、深入探索

对项目的路由改造到此就结束了么?回过头来再想想,发现还是存在一些问题:

  • 需要手动创建并维护不同的路由管理模块(HomeRouteContainerSettingRouteContainer)
  • 新的页面都需要在对应的模块类中进行手动注册

客户端原生项目对于这类问题,可以通过注解的方式解决,类似阿里的ARouter,那么Flutter也可以借鉴此方式完成进一步的优化,利用注解去生成对应的路由模块管理文件,避免手动维护,具体如下:

5.1、注解子路由表
const String HOME_ROUTE_TABLE = 'HomeRouteTable';
const String SETTING_ROUTE_TABLE = 'SettingsRouteTable';

//tDescription: 仅仅作为生成类的注释
@RouterTableList(
  tableList: [
    RouterTable(tName: HOME_ROUTE_TABLE, tDescription: '大厅路由模块'),
    RouterTable(tName: SETTING_ROUTE_TABLE, tDescription: '设置路由模块'),
  ],
)


//with HomeRouterTable, MineRouterTable,即上面声明的两个路由表的名字
class AppRouteContainer extends MixinRouterInterceptContainer
    with HomeRouteTable, SettingsRouteTable {
  AppRouteContainer._();

  static AppRouteContainer _instance = AppRouteContainer._();

  static AppRouteContainer get share => _instance;
}
5.2、注解普通路由
//方式一:不使用路由参数
@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_a')
class APage extends StatelessWidget {
  const APage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('APage'),
    );
  }
}

//方式二:使用路由参数
@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_a', arg=true)
class APage extends StatelessWidget {
  const APage(dynamic args, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('APage'),
    );
  }
}
5.3、注解拦截路由
@MixinRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_b')
class BPage extends StatelessWidget {
  const BPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('BPage'),
    );
  }
}

@MixinInterceptRoute(tName: SETTING_ROUTE_TABLE, path: '/setting_b')
bool interceptorMinePage(context, pageName, pushType, {arguments, predicate}) { //函数签名固定写法
  print('toLogin');
  return true;
}

6、集成使用

在项目的pubspec.yaml中添加依赖,即可开启注解路由之旅

dependencies:
  flutter:
    sdk: flutter
  flutter_mixin_router: ^1.0.0      # 添加路由模块管理基类
  flutter_mixin_router_ann: 1.0.0   # 添加注解类

dev_dependencies:
  build_runner: 2.1.8               # 添加依赖
  flutter_mixin_router_gen: 1.0.1   # 添加代码生成工具库

在项目页面上添加对应的注解后,执行以下命令生成对应的路由代码

# 清除增量编译缓存
flutter packages pub run build_runner clean

# 重新生成代码
flutter packages pub run build_runner build --delete-conflicting-outputs

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

1 回复

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


flutter_mixin_router 是一个用于 Flutter 应用的路由管理插件,它通过 Mixin 的方式简化了路由的管理和跳转。使用这个插件,你可以更方便地处理页面之间的导航,并且可以避免手动管理路由栈的复杂性。

以下是如何使用 flutter_mixin_router 的基本步骤:

1. 添加依赖

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

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

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

2. 创建路由管理类

创建一个路由管理类,并使用 MixinRouter 来混入路由管理功能。

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

class AppRouter with MixinRouter {
  static const String home = '/';
  static const String details = '/details';

  [@override](/user/override)
  Map<String, WidgetBuilder> get routes => {
        home: (context) => HomePage(),
        details: (context) => DetailsPage(),
      };
}

3. 在 MaterialApp 中使用路由管理

在你的 MaterialApp 中使用 AppRouter 来管理路由:

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

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

class MyApp extends StatelessWidget {
  final AppRouter _router = AppRouter();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Mixin Router Demo',
      initialRoute: AppRouter.home,
      onGenerateRoute: _router.generateRoute,
    );
  }
}

4. 页面跳转

在页面中,你可以使用 MixinRouter 提供的 navigateTo 方法来进行页面跳转:

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

class HomePage extends StatelessWidget {
  final AppRouter _router = AppRouter();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            _router.navigateTo(context, AppRouter.details);
          },
          child: Text('Go to Details'),
        ),
      ),
    );
  }
}

class DetailsPage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Details Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back'),
        ),
      ),
    );
  }
}

5. 处理带参数的路由

如果你需要传递参数到目标页面,可以在 navigateTo 方法中传递参数:

_router.navigateTo(context, AppRouter.details, arguments: {'id': 123});

在目标页面中,你可以通过 ModalRoute.of(context)!.settings.arguments 来获取参数:

class DetailsPage extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    final arguments = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
    final id = arguments['id'];

    return Scaffold(
      appBar: AppBar(
        title: Text('Details Page'),
      ),
      body: Center(
        child: Text('ID: $id'),
      ),
    );
  }
}
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!