法法路由 4.0

前言

法法路由发布1年多了,总感觉有一些弊端,或者说用起来不舒服的地方。

  • 之前是利用命名参数,通过路由的 arguments (Map),利用 key 设置对应的命名参数。需要去注解参数的名字,如下图。
import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFRoute(
  name: "fluttercandies://picswiper",
  routeName: "PicSwiper",
  argumentNames: ["index", "pics"],
  showStatusBar: false,
  pageRouteType: PageRouteType.transparent,
)
class PicSwiper extends StatefulWidget {
  final int index;
  final List pics;
  PicSwiper({this.index, this.pics});
  // ...
}

  • 因为是命名参数,参数也可能是可选非必填。 但是生成的代码中,我不知道你有没有传递这个参数。但是根据注解生成的代码依然会从 arguments 中去试图拿到参数对应的值,这就会造成这个参数被设置成 null。所以在平时使用的时候,就必须在 pushName 的时候将可选参数也一起传递才行。

  • 如果一个页面有多个构造,那怎么办呢?是否可以选择使用哪个构造呢?之前是不支持的。

  • 构造如果是 const 的,之前是没法在生成的代码中也创建 const 的构造。

4.0

在 4.0 版本中,我解决了上面4个问题。

  • 通过对 analyzer, ast 更多的了解,通过 ast 我们可以直接拿到 ConstructorDeclaration ,它是对构造的定义. 代码地址 analyzer/lib/dart/ast/ast.dart , 下面是主要用的一些参数。
abstract class ConstructorDeclaration implements ClassMember {

  /// Return the token for the 'const' keyword, or `null` if the constructor is
  /// not a const constructor.
  /// 构造是否是 const
  Token get constKeyword;

  /// Set the token for the 'const' keyword to the given [token].
  set constKeyword(Token token);

  /// Return the name of the constructor, or `null` if the constructor being
  /// declared is unnamed.
  /// 构造的名字,默认构造这个值为null
  /// factory TestPageE.deafult(), 这种的话,返回 deafult
  SimpleIdentifier get name;

  /// Set the name of the constructor to the given [identifier].
  set name(SimpleIdentifier identifier);

  /// Return the parameters associated with the constructor.
  /// 返回参数的集合
  FormalParameterList get parameters;

  /// Set the parameters associated with the constructor to the given list of
  /// [parameters].
  set parameters(FormalParameterList parameters);
}
  • FormalParameter 地址 analyzer/lib/dart/ast/ast.dart , 这是用来描述参数的. 参数的意思都很清楚,这里就不翻译了。
abstract class FormalParameter implements AstNode {
  /// The 'covariant' keyword, or `null` if the keyword was not used.
  Token get covariantKeyword;

  /// Return the element representing this parameter, or `null` if this
  /// parameter has not been resolved.
  ParameterElement get declaredElement;

  /// Return the name of the parameter being declared.
  SimpleIdentifier get identifier;

  /// Return `true` if this parameter was declared with the 'const' modifier.
  bool get isConst;

  /// Return `true` if this parameter was declared with the 'final' modifier.
  ///
  /// Parameters that are declared with the 'const' modifier will return
  /// `false` even though they are implicitly final.
  bool get isFinal;

  /// Return `true` if this parameter is a named parameter.
  ///
  /// Named parameters can either be required or optional.
  bool get isNamed;

  /// Return `true` if this parameter is an optional parameter.
  ///
  /// Optional parameters can either be positional or named.
  bool get isOptional;

  /// Return `true` if this parameter is both an optional and named parameter.
  bool get isOptionalNamed;

  /// Return `true` if this parameter is both an optional and positional
  /// parameter.
  bool get isOptionalPositional;

  /// Return `true` if this parameter is a positional parameter.
  ///
  /// Positional parameters can either be required or optional.
  bool get isPositional;

  /// Return `true` if this parameter is a required parameter.
  ///
  /// Required parameters can either be positional or named.
  ///
  /// Note: this will return `false` for a named parameter that is annotated
  /// with the `@required` annotation.
  bool get isRequired;

  /// Return `true` if this parameter is both a required and named parameter.
  ///
  /// Note: this will return `false` for a named parameter that is annotated
  /// with the `@required` annotation.
  bool get isRequiredNamed;

  /// Return `true` if this parameter is both a required and positional
  /// parameter.
  bool get isRequiredPositional;

  /// Return the kind of this parameter.
  @deprecated
  ParameterKind get kind;

  /// Return the annotations associated with this parameter.
  NodeList get metadata;

  /// The 'required' keyword, or `null` if the keyword was not used.
  Token get requiredKeyword;
}
  • 上面4个问题白嫖官方,自己再把逻辑搞清楚就行了,ast 很强力。但是也发现一个问题,想判断下这个参数是否是 dart core 库的,因为如果是类或者枚举,我希望能有个提示,让用户设置 import 地址。

虽然可以扫描全部的 dart 文件,但是考虑到还有三方引用,所以没这样做。找到 DartType 参数,但是这个值一直为 null,最后用 dart:mirrors 反射做了,但是发现最后因为 Flutter 要引入法法路由,会报错,就放弃了。

话说,法法路由只是 dev_dependencies 中引用,为啥会影响 Flutter 的运行时呢?ff_annotation_route 中暴露了一些类给 Flutter 使用,所以说运行时 ff_annotation_route 也是参加编译了? 有大佬知道吗?

dev_dependencies:
  ff_annotation_route:
    path: ../

使用

增加引用

添加引用到dev_dependencies,及你需要注解的 project/packages 到pubspec.yaml

dev_dependencies:
  ff_annotation_route: latest-version

执行 flutter packages get 下载

添加注解

空构造

import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFRoute(
  name: "fluttercandies://mainpage",
  routeName: "MainPage",
)
class MainPage extends StatelessWidget
{
  // ...
}

带参数构造

工具会自动处理带参数的构造,不需要做特殊处理。唯一需要注意的是,你需要使用 argumentImports 为class/enum的参数提供 import 地址。

import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFRoute(
  name: 'flutterCandies://testPageE',
  routeName: 'testPageE',
  description: 'This is test page E.',
  argumentImports: [
    'import \'package:example/src/model/test_model.dart\';',
    'import \'package:example/src/model/test_model1.dart\';'
  ],
  exts: {
    'group': 'Complex',
    'order': 1,
  },
)
class TestPageE extends StatelessWidget {
  const TestPageE({
    this.testMode = const TestMode(
      id: 2,
      isTest: false,
    ),
    this.testMode1,
  });
  factory TestPageE.deafult() => TestPageE(
        testMode: TestMode.deafult(),
      );

  factory TestPageE.required({@required TestMode testMode}) => TestPageE(
        testMode: testMode,
      );

  final TestMode testMode;
  final TestMode1 testMode1;
}

FFRoute

Parameter Description Default
name 路由的名字 (e.g., "/settings") required
showStatusBar 是否显示状态栏 true
routeName 用于埋点收集数据的页面名字 ''
pageRouteType 路由的类型 (material, cupertino, transparent) -
description 路由的描述 ''
exts 其他扩展参数. -
argumentImports 某些参数的导入.有一些参数是类或者枚举,需要指定它们的导入地址 -

生成文件

环境

添加 dart 的 bin 的路径到你的系统 $PATH.

cache\dart-sdk\bin

更多信息

不清楚的可以看掘金

激活

pub global activate ff_annotation_route

执行命令

到你的项目根目录下面执行.

ff_route [arguments]

命令参数

可用的命令:

command name description
-h, --help 打印帮助信息.
-p, --path [arguments] 执行命令的目录,没有就是当前目录.
-rc, --route-constants 是否在根项目中的 xxx_route.dart 生成全部路由的静态常量
-rh, --route-helper 生成 xxx_route_helper.dart 来帮助你处理路由
-rn, --route-names 是否在根项目中的 xxx_route.dart 生成全部路由的名字
-s, --save 是否保存命令到本地,如果保存了,下一次就只需要执行ff_route就可以了
-na, --no-arguments FFRouteSettings 将没有 arguments 这个参数,这个是主要是为了适配 Flutter 低版本
-g, --git package1,package2 是否扫描 git 引用的 package,你需要指定 package 的名字
--package 这个是否是一个 package
--no-is-initial-route FFRouteSettings 将没有 isInitialRoute 这个参数,这个是主要是为了适配 Flutter 高版本
-o --output route和helper文件的输出目录路径,路径相对于主项目的lib文件夹
-rfo --routes-file-output routes 文件的输出目录路径,路径相对于主项目的lib文件夹

Main.dart

  • 如果运行的命令带有参数 --route-helper , FFNavigatorObserver/FFRouteSettings
    将会生成在 xxx_route_helper.dart 中,用于协助追踪页面和设置状态栏。

  • 如果运行的命令带有参数 --route-helperFFTransparentPageRoute 将会生成在
    xxx_route_helper.dart 中,可以使用它来 push 一个透明的 PageRoute

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage,
      onGenerateRoute: (RouteSettings settings) {
        //when refresh web, route will as following
        //   /
        //   /fluttercandies:
        //   /fluttercandies:/
        //   /fluttercandies://mainpage
        if (kIsWeb && settings.name.startsWith('/')) {
          return onGenerateRouteHelper(
            settings.copyWith(name: settings.name.replaceFirst('/', '')),
            notFoundFallback:
                getRouteResult(name: Routes.fluttercandiesMainpage).widget,
          );
        }
        return onGenerateRouteHelper(settings,
            builder: (Widget child, RouteResult result) {
          if (settings.name == Routes.fluttercandiesMainpage ||
              settings.name == Routes.fluttercandiesDemogrouppage) {
            return child;
          }
          return CommonWidget(
            child: child,
            result: result,
          );
        });
      },
    );
  }
}

Push

Push name

  Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);

Push name with arguments

参数必须是一个 Map

  Navigator.pushNamed(
    context,
    Routes.flutterCandiesTestPageE,
    arguments: {
      constructorName: 'required',
      'testMode': const TestMode(
        id: 100,
        isTest: true,
      ),
    },
  );

Code Hints

你能这样使用路由 'Routes.flutterCandiesTestPageE', 并且在编辑器中看到代码提示。
包括页面描述,构造,参数类型,参数名字,参数是否必填。

  /// 'This is test page E.'
  ///
  /// [name] : 'flutterCandies://testPageE'
  ///
  /// [routeName] : 'testPageE'
  ///
  /// [description] : 'This is test page E.'
  ///
  /// [constructors] :
  ///
  /// TestPageE : [TestMode testMode, TestMode1 testMode1]
  ///
  /// TestPageE.deafult : []
  ///
  /// TestPageE.required : [TestMode(required) testMode]
  ///
  /// [exts] : {group: Complex, order: 1}
  static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';

结语

命名路由的另外一个问题就是参数不是强类型输入,对于使用者可能没有直接 new 构造器来的清晰舒服。但是我还是觉得不要再引入额外的参数类耦合代码,毕竟鱼和熊掌不可兼得。命名路由+注解能够有效的统一处理路由,还可以增加一些额外的辅助功能,还是挺香的。

欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果( flutter-candiesQQ群:181398081)

最最后放上Flutter Candies全家桶,真香。

[图片上传失败...(image-76d78f-1600164063164)]

你可能感兴趣的:(法法路由 4.0)