Background:

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. This article provides 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 logical stack: Poor implementation of mapping is mapping urls to the corresponding widget instance by if-else logical judgment,
class Router {
    Widget route(String url, Map params) {
        if(url == 'myapp://apage') {
            return PageA(url);
        } else if(url == 'myapp://bpage') {
            returnPageB(url, params); }}}Copy the code

The disadvantages of this are obvious: 1) maintenance each mapping affect the stability of the global mapping configuration, each time to maintain the mapping management need to imagine all the branches of logic. 2) cannot achieve the unity of the abstract page, the page’s constructor and tectonic custom logic by developers. 3) linkage mapping configuration with the page, the page level configuration for centralized maintenance, leading to lack of maintenance responsibility.

  1. A general implementation, manually maintained mapping table: slightly better is 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. Annotation_route (github :github.com/alibaba-flu…) As a result, the operation system of the whole annotation scheme is shown in the figure below:




Let’s start with the DART annotation to see how the system works.


The dart annotations

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.

source_gen

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
  • The source_gen library provides interception of annotation elements. Here is a brief introduction to Source_gen and its upstream and downstream, and we have a look at the annotation class diagram:





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. Source_gen thus hands over the construction of a file to multiple generators it defines, while providing a friendlier wrapper to build libraries.

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.

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:

  1. Only one file needs to be generated: Since an input file corresponds to a build file suffix, we need to avoid redundant file generation
  2. 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
  3. 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. Annotation_route: annotation_route: annotation_route: annotation_route: annotation_route: annotation_route

 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(PageDOption(urlString, query));
        } else if(urlString == 'myapp://testf') {
            return TestF(PageDOption(urlString, query));
        } else if(urlString == 'myapp://testg') {
            return TestG(PageDOption(urlString, query));
        } else if(urlString == 'myapp://testh') {
            return TestH(PageDOption(urlString, query));
        } else if(urlString == 'myapp://testi') {
            return TestI(PageDOption(urlString, query));
        }
        returnDefaultWidget; }}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;
        }
        returnDefaultWidget; }}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. For detailed instructions on annotation_route installation and use, see github at github.com/alibaba-flu… If you have any problems in use, please feel free to give us feedback.




Read the original

This article is the original content of the cloud habitat community, shall not be reproduced without permission.