01
In the process of Flutter business development, Flutter side will gradually enrich its own route management. A lightweight routing management is essentially a mapping of page identifiers (or page paths) to page instances. In this article, Xianyu engineers provide a lightweight routing management solution based on the DART annotations.
In both native and Flutter hybrid projects and pure Flutter development projects, there are several ways to implement a lightweight route:
1. Poor implementation, if-else logic stack:
A poor implementation of mapping is to map the URL to the corresponding widget instance using if-else logic.
class Router {
Widget route(String url, Map params) {
if(url == 'myapp://apage') {
return PageA(url);
} else if(url == 'myapp://bpage') {
return PageB(url, params);
}
}
}
Copy the code
The disadvantages of this approach are obvious: 1) The maintenance of each mapping affects the stability of the global mapping configuration, and each maintenance of the mapping management requires imagining all logical branches. 2) The unified abstraction of the page cannot be achieved, and the constructor and construction logic of the page are customized by the developer. 3) The mapping configuration cannot be associated with the page, and the page-level configuration is centrally maintained, leading to the loss of maintenance personnel.
2. General implementation, manually maintained mapping table:
It is slightly better to represent the mapping through a configuration information and a factory method.
class Router {
Map<String, dynamic> mypages = <String, dynamic> {
'myapp://apage': 'pagea',
'myapp://bpage': 'pageb'
}
Widget route(String url, Map params) {
String pageId = mypages[url];
return getPageFromPageId(pageId);
}
Widget getPageFromPageId(String pageId) {
switch(pageId) {
case 'pagea': return PageA();
case 'pageb': return PageB();
}
return null;
}
Copy the code
This is still cumbersome on the Flutter side, first because problem 3 still exists, and second because Flutter currently does not support reflection, there must be a factory approach to creating page instances.
To solve these problems, we need a solution that can be used and maintained automatically at the page level, and annotations are a worthwhile direction to go. Our routing annotation scheme annotation_route emerged at the historic moment, and the running system of the whole annotation scheme is shown in the figure:
Let’s start with the DART annotation to see how the system works.
02
Annotations are actually a code level configuration that can be applied either at compile time or at runtime. Since Flutter does not currently support runtime reflection, we need to obtain information about annotations at compile time to generate an automatically maintained mapping table. What we need to do is to analyze the syntax structure of the DART file during compilation, find the annotation block and relevant content of the annotations in the file, collect the annotation content, and finally generate the mapping table we want. The idea of this scheme is as shown in the figure:
Some of dart’s built-in libraries were found to accelerate implementation.
03
Dart provides build, Analyser, and source_gen libraries, with source_gen using the Build and Analyser libraries to provide a better encapsulation of annotation interception. In terms of annotation capabilities, the three libraries provide the following capabilities:
-
Build library: processing the whole set of resource files
-
Analyser library: Generates a complete syntax structure for the DART file
-
Source_gen library: Provides interception of annotation elements
Here’s a quick look at source_gen and its upstream and downstream, starting with the class diagram we pulled out for its annotations:
The source of source_gen is the Base Builder class provided by the Build library. This class allows users to customize the resource files they are working on, provides information about the resource files, and provides methods for generating new resource files. Source_gen derived its own Builder from the Builder class provided by the Build library, as well as a custom set of abstractions for the Generator. The derived Builder takes a collection of Generator classes and collects the output of the Generator. Finally, a file is generated, and different derived Builders treat generator differently. In this way, source_ gen hands over the construction of a file to multiple generators it defines, while providing a more build-friendly wrapper.
On top of the abstract Generator, Source_gen provides annotation-specific Generator GeneratorForAnnotation, where an annotation Generator instance accepts a specified annotation type, Since the Analyser provides the abstract Element Element of the grammar node and its metadata field, the annotation’s syntax abstraction Element ElementAnnotation, the annotation generator can check that the metadata type of each Element matches the declared annotation type, To filter out information about the annotated element and its context, and then wrap this information to the user, we can use this information to complete routing annotations.
04
annotation_route
After learning about source_gen, we started working on annotation_route, our own annotation parsing scheme. When it first started, we ran into a few problems:
-
Only one file needs to be generated: Since an input file corresponds to a build file suffix, we need to avoid redundant file generation
-
Need to know when to generate files: do we need to wait until all alternative files have been scanned and collected before we can generate the mapping table
-
Source_gen only supports one annotation for a class, but there are multiple urls mapped to a page. After some thought we came up with the following
The annotations are first divided into two categories, one for the annotation page @aroute and the other for the annotation user’s own router@ARouteRoot. RouteBuilder has RouteGenerator instance, RouteGenerator instance, which is responsible for @aroute annotation; RouteWriteBuilder has an instance of RouteWriterGenerator that is responsible for the @arouteroot annotation. Yaml configuration file supported by the build library, control the construction order of the two types of Builder, execute routeWriteBuilder after routeBuilder execution is complete, so that we can accurately start to generate our own configuration file after all the page annotation scanning is complete.
In the annotation parsing project, for @aroute annotated pages, the configuration information is sent to the Collector with static storage via RouteGenerator and the output is set to NULL, meaning that no corresponding file is generated. After all the @aroute annotated pages have been scanned, RouteWriteGenerator calls Writer, which extracts the information from the Collector and generates the final configuration file. For users, we provide a friendly wrapper, and after configuring annotation_route to the project, our routing code changes like this:
Before use:
import 'testa.dart'
import 'testb.dart'
import 'testc.dart'
import 'testd.dart'
import 'teste.dart'
import 'testf.dart'
class Router {
Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) {
if(urlString == 'myapp://testa') {
return TestA(urlString, query);
} else if(urlString == 'myapp://testb') {
String absoluteUrl = Util.join(urlString, query);
return TestB(url: absoluteUrl);
} else if(urlString == 'myapp://testc') {
String absoluteUrl = Util.join(urlString, query);
return TestC(config: absoluteUrl);
} else if(urlString == 'myapp://testd') {
return TestD(PageDOption(urlString, query));
} else if(urlString == 'myapp://teste') {
return TestE(PageEOption(urlString, query));
} else if(urlString == 'myapp://testf') {
return TestF(PageFOption(urlString, query));
}
return DefaultWidget;
}
}
Copy the code
After use:
import 'package:annotation_route/route.dart';
class MyPageOption {
String url;
Map<String, dynamic> query;
MyPageOption(this.url, this.query);
}
class Router {
ARouteInternal internal = ARouteInternalImpl();
Widget pageFromUrlAndQuery(String urlString, Map<String, dynamic> query) {
ARouteResult routeResult = internal.findPage(ARouteOption(url: urlString, params: query), MyPageOption(urlString, query));
if(routeResult.state == ARouteResultState.FOUND) {
return routeResult.widget;
}
return DefaultWidget;
}
}
Copy the code
At present, this scheme has been running stably in Xianyu APP. We have provided basic routing parameters. As Flutter business scenarios become more and more complex, we will further explore the freedom of annotations. Instructions regarding use annotation_route more detailed installation and see lot address: https://github.com/alibaba-flutter/annotation_route, have any problems in use, welcome to our feedback.
In depth | 10 minutes to read alibaba senior expert’s share on Flutter Live2018
TensorFlow app “UI 2 Code”
Thousands of online problem playback technology revealed
2018 Double 11· Real-time selection of top goods and excellent products — “Mach”
Pay attention to two-dimensional code, forward-looking technology is in control