前言
法法路由发布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
命令参数
可用的命令:
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-helper
,FFTransparentPageRoute
将会生成在
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小糖果( QQ群:181398081)
最最后放上Flutter Candies全家桶,真香。
[图片上传失败...(image-76d78f-1600164063164)]