Have you ever encountered repetitive, template code in your daily development, such as fromJson/toJson methods for data models, or counting the execution time of each method? This kind of code is not difficult and trivial, which makes us work overtime and not happy to fish. The good news is that There are technologies similar to native APT and AOP in Flutter. What are their characteristics? Which solution should be used? How do they generate code? And looked down

What are APT and AOP

1. APT (Annotation Processing Tool) Annotation Processing Tool

APT in Flutter usually refers to source_gen, which generates visible code in the code editing phase through custom annotations and handlers.

Applications based on this include: Json_serializable is a very useful JSON parsing library that automatically generates fromJson/toJson methods for us by adding @jsonClass annotations to the model. It also supports property aliases and complex List structures. Such as:

The source code:@JsonClass(a)class CityModel {
  @JsonField(["cityCode"])
  String cityCode;
  @JsonField(["cityName"])
  String cityName;
  
  CityModel();
  
  factory CityModel.fromJson(Map<String.dynamic> json) {
    return _$CityModelFromJson(json);
  }
  Map<String.dynamic> toJson() {
    return _$CityModelToJson(this); }}Copy the code

perform

flutter packages pub run build_runner build --delete-conflicting-outputs
Copy the code

Automatically generate the corresponding parsing method:

CityModel _$CityModelFromJson(Map<String.dynamic> json) {
  CityModel instance = CityModel();
  instance.cityCode =
      parseFieldByType<String>(json['cityCode'], instance.cityCode);
  instance.cityName =
      parseFieldByType<String>(json['cityName'], instance.cityName);

  return instance;
}

Map<String.dynamic> _$CityModelToJson(CityModel instance) => <String.dynamic> {'cityCode': instance.cityCode,
      'cityName': instance.cityName,
    };
Copy the code

2, AOP (aspect-oriented Programming) Aspect Oriented Programming

APT in Flutter generally refers to Aspectd. The specified code can be inserted in the Build phase of a Flutter product through the PointCut anywhere.

See Demo in AspectD and use the @execute annotation to run the package application. Output KWLM called! When _MyHomePageState calls _incrementCounter .

import 'package:aspectd/aspectd.dart';

@Aspect(a)@pragma("vm:entry-point")
class ExecuteDemo {
  @pragma("vm:entry-point")
  ExecuteDemo();

  @Execute("package:example/main.dart"."_MyHomePageState"."-_incrementCounter")
  @pragma("vm:entry-point")
  void _incrementCounter(PointCut pointcut) {
    pointcut.proceed();
    print('KWLM called! '); }}Copy the code

Second, technical comparison

It looks like both technologies have the ability to generate code, so what’s the difference?

In terms of the entire code lifecycle from authoring to running:

APT technique is used in the code editing phase, after executing the command, it will filter out all source_gen execution scripts in the current repository dependencies. Encapsulate the code in the current package as an entry and submit it to all code handlers. The generated content is visible at the edit stage, for example the parsing code above generates an additional new file:

AOP technology modifies the original compiling process of Flutter_tools in product compiling stage. His input is a dILL file of the entire Flutter code compiled in frontend_server. An AOP processor is used for secondary processing, and the newly generated DILL file continues to execute the subsequent compilation process.

(Image quote:The SDK implementation of Flutter has no buried point)

Because all the FLUTTER code is included in the DILL product, AOP can generate code anywhere, including in the FLUTTER /Dart SDK.

A comparison of the two technologies:

Compare the item APT(source_gen) AOP(AspectD)
Action stage Code editing phase Complete product construction phase
The input Current single package The dILL file from which all the code is compiled
The output New file or no output New DILL file
Whether generated code is visible visible invisible
Whether to modify Flutter_tools no is
Whether hot_reload is valid is no
Can YOU modify the SDK? no is

When to use APT/AOP?

From the above knowledge, we can see that both techniques can do code generation, so how to decide which one to use? Analysis through two scenarios:

Json parsing: APT instead of AOP

The JSON annotation generation mentioned above is based on APT, but the AOP solution can also be done and is relatively more elegant (because it does not affect the business engineering). But its biggest problem is that AOP generated code is erased after hot reload/restart.

As mentioned earlier, AOP scenarios are executed during the full artifact construction phase, but hot overloading does not go through this process. When an overload pushes a new artifact into the APP, there is no AOP generated content. This means that if you use an AOP solution, you need to package and build your APP artifacts each time, losing the efficient development capabilities that thermal overloading brings.

While APT generated code is visible in the editing stage, in fact, there is no difference with handwritten code. APT just saves us that work, so hot reloading works as well.

2, statistical method time-consuming: AOP instead of APT

For example, if we want to count the statistical time in all codes, in fact, we insert the statistical code before and after the method call. In theory, APT can also do this. But APT has several limitations:

  • APT can only generate code based on the current package and cannot be inserted into third party dependencies.

  • APT explicitly invades the business layer to insert code.

This is where the advantage of an AOP solution becomes apparent, generating code without the business code being sensitive and overwriting code from third parties or even from the SDK.

Therefore, the choice of which scheme can refer to the two comparison, combined with the actual needs to choose.


Analysis of core process of code generation

Seeing this, you should have a general idea of how to choose between the two technologies. For details on how to use APT, you can refer to: Flutter annotation handling and code generation, and AOP: Flutter unburied SDK implementation.

However, these two technologies still have disadvantages, such as the long code generated by APT and the complexity of debugging. AOP thermal overload will fail, etc. Can we look at the key design and address these issues? In this section, we’ll take a look at their core process, but let’s leave all the scenarios behind and think about code generation.

1. What is the nature of code generation?

Regardless of the technical details, code generation can be understood as: “A source code, through the processor, generates a new code.”

There are really three key points: “input, processing, and output”, which is the essence of the difference between different solutions.

The simplest and most straightforward is to scan for string identifiers with a Python or shell script, for example, traversing a project file with @test to manipulate the file.

The advantage of this is its generality: you can do this in a similar way in any language, with string matching.

The disadvantages are also obvious, as there is a lot of logic to be written to identify the morphology/grammar in a particular language because it is language independent.

Third party libraries such as APT and AOP identify the content (Class, Field, Construcor, etc.) based on the language’s writing rules, giving us easy access to the code.

2. APT (Source_gen) workflow

The APT workflow can be traced along the implementation of the write processor. We typically inherit GeneratorForAnnotation

, where

represents the annotation that the handler is looking for. Then override the corresponding generation method, such as returning a Hello World directly.

Element in this method represents the Element of a Class or Mixin to get all the attributes, methods, constructors, and so on in any Class or Mixin annotated by @routemap.

This is the result of lexical/syntactic analysis of source code in APT (source_gen), so that we can operate the code directly.

Looking up at GeneratorForAnnotation

you can see:

In the generate(LibraryReader library, BuildStep BuildStep) method, a libary is given. A libary represents a code file. All elements containing this annotation are then filtered by the generic T to be passed to subclasses for processing. (So this technique actually works without annotations, since it can access the concrete Element object.)

A new file is then written based on the string result returned by the subclass.

So, how the source code is organized into libary structure, must be a lexical/grammatical analysis.

In the whole APT (source_gen) call, there is such a node

Buildstep.inputlibrary calls resolver to parse inputId and return LibaryElement.

The Resolver creates a LibaryElement file for each AssetId.

AssetId corresponds to individual file paths in a project, generated by drvier on the last line, which performs lexical and grammatical analysis on the files. The core processes are in DART/Analyzer.

For the complete process, please refer to “Flutter code generation source_gen usage and Principle analysis”

AOP (AspectD) workflow

AOP details are more, need some AST knowledge, this main process comb, follow up to open a series of detailed analysis.

Aop is the process of building flutter artifacts. When font_server is compiled, a DILL file (understood as bytecode in Android) is generated, and the original artifacts are processed and replaced by modifying Flutter_Tools to execute the code in AspectD.

Execute to AspectD through processManager

The main steps are as follows

    /// 1. Read the DILL file
    final Component component = dillOps.readComponentFromDill(intputDill);

    /// Parsing programs that include Aspect annotations in all dependencies of the project
    _resolveAopProcedures(libraries);

    ///* * * ** * * ** * * ** Save a lot of code
    
    /// 3. Execute Execute/Inject annotation code generation according to the results of the previous step
    /// Aop execute transformer
    if(executeInfoList.isNotEmpty) { AopExecuteImplTransformer(executeInfoList, libraryMap).. aopTransform(); }/// Aop inject transformer
    if(injectInfoList.isNotEmpty) { AopInjectImplTransformer(injectInfoList, libraryMap, concatUriToSource) .. aopTransform(); }/// Write the processed Component object back to the previous DILL path
    dillOps.writeDillFile(component, outputDill);
Copy the code

In the second step, how to find the annotation handler of @aspect will be found by tracing the source code, which is to obtain the corresponding code of annotation program such as @execute@Inject through character matching through all files, and then store it into the collection for execution.

/// Gets the DEFINED AOP type by string matching
static AopMode getAopModeByNameAndImportUri(String name, String importUri) {
    ///*** Omit similar code
    if (name == kAopAnnotationClassExecute &&
        importUri == kImportUriAopExecute) {
      return AopMode.Execute;
    }
    if (name == kAopAnnotationClassInject && importUri == kImportUriAopInject) {
      return AopMode.Inject;
    }
    ///* * * *
    }
Copy the code

However, the method of code generation here is different from APT, source_gen above generates code through string, while Apsectd uses Expression and Statement to build code logic, which will be further studied in the next series.

Finally write the modified Component to the DILL file.


Five, the summary

Article highlights:

1. Code editing lifecycle and code generation scheme

2. Compare the two technologies to choose a solution suitable for the problem.

Compare the item APT(source_gen) AOP(AspectD)
Action stage Code editing phase Complete product construction phase
The input Current single package The dILL file from which all the code is compiled
The output New file or no output New DILL file
Whether generated code is visible visible invisible
Whether to modify Flutter_tools no is
Whether hot_reload is valid is no
Can YOU modify the SDK? no is

The nature of code generation

4. Core API

  • LibraryElement Element = resolver.libraryfor (path)
  • Parsing dill file: Component Component = dillOps. ReadComponentFromDill (intputDill);

Six, the next notice

Dart 2.15 brings in concurrent programming based on isolate efficient mechanism: dry | Dart concurrent mechanism explanation, seeing the Flutter derivatives, there is Future/Timer/Microtask event mechanism, etc. How do they do it? What is the relationship with virtual machines? What are their characteristics?

In the next series, we will learn about Flutter virtual machines and event mechanisms, starting with use-source analysis. It may be published in the form of columns or pamphlets, so stay tuned.

Ps: The next series is not a small challenge, I will be closed for a period of time, I hope to make a breakthrough within a month.

This article was first published on my official account: Progressive Flutter or Runflutter, which collects the most detailed guide to Flutter progression and optimization. Welcome to follow.

If you have any questions, please contact me through the official account. If the article inspires you, I hope to get your thumbs up, attention and collection, which is the biggest motivation for me to continue writing. Thanks~

Past highlights:

Advance optimization of Flutter

The Flutter core rendering mechanism

Flutter routing design and source code analysis