preface

When LEARNING Android, I used APT to parse annotations at compile time and then generated code with Javapoet. In fact, there is a similar technology for Flutter. Source_gen and Code_Builder are responsible for parsing annotations at compile time. Code_builder is responsible for generating code. This article explains how to use source_gen and code_Builder to generate code at compile time

Specific steps

1. create a new flutter package, named Annotations

Create a new annotation class in this package for annotations, as shown below

class ChannelHelp { final String channelName; const ChannelHelp(this.channelName); // Annotation class, constructor must be Const}Copy the code

2. Create a flutter package and name it Generate

This package is specifically used to parse annotations, generating code that relies on Annotations from the package created above in pubspec.yaml, and then adds dependencies on source_gen and build_runner as follows

Name: generate description: A new Flutter package. Version: 0.0.1 author: homepage: environment: SDK: ">=2.7.0 <3.0.0" flutter: ">=1.17.0 <2.0.0" flutter: Annotations: ^0.9.6 # / Annotations dev_dependencies: flutter_test: SDK: build_runner: ^1.10.0Copy the code

3. Create the Dart class ChannelHelpGenerator in the Generate package

This class inherits from ChannelHelpGenerator and parses ChannelHelp annotations as follows:

import 'package:analyzer/dart/element/element.dart'; import 'package:annotations/channel_help.dart'; import 'package:build/src/builder/build_step.dart'; import 'package:source_gen/source_gen.dart'; import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:path/path.dart' as Path; import 'package:build/build.dart'; class ChannelHelpGenerator extends GeneratorForAnnotation<ChannelHelp> { static final String channelName = "_MethodChannel"; String className; @override generateForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep) { final emitter = DartEmitter(); if (element is! ClassElement) {throw InvalidGenerationSourceError (' ChannelHelper used only on class '); } className = element.displayName; var channelName = annotation.peek("channelName").stringValue; ClassBuilder classBuilder; var channelHelper = Class((builder) { classBuilder = builder; classBuilder.constructors.add(Constructor((constructorBuild) { constructorBuild.name = "_internal"; })); classBuilder.name = '${className}Imp'; classBuilder.implements.add(refer(className)); classBuilder.fields.add(Field((fieldBuild) { fieldBuild.name = "_MethodChannel"; fieldBuild.type = refer("MethodChannel"); fieldBuild.modifier = FieldModifier.final$; fieldBuild.assignment = Code('MethodChannel("$channelName")'); })); classBuilder.fields.add(Field((fieldBuild) { fieldBuild.name = "_$className"; fieldBuild.type = refer("$className"); fieldBuild.static = true; })); classBuilder.methods.add(Method((methodBuild) { methodBuild.name = "getInstance"; methodBuild.returns = refer('$className'); methodBuild.static = true; methodBuild.body = _generatorSingleInstantBody(); })); ClassElement classElement = element as ClassElement; List<MethodElement> methodElements = classElement.methods; if (methodElements ! = null && methodElements.length > 0) { methodElements.forEach((methodElement) { classBuilder.methods.add(Method((methodBuild) { methodBuild.name = methodElement.name; methodBuild.modifier = MethodModifier.async; methodBuild.returns = refer("${methodElement.returnType.getDisplayString()}"); MethodBuild. Annotations. The add (TypeReference ((build) {/ / add annotations to method build. The symbol = "override"; // The annotation type is override. var parameters = methodElement.parameters; methodBuild.body = _generatorBody(methodElement, parameters); })); }); }}); String channelHelperStr = DartFormatter().format('${channelHelper.accept(emitter)}'); return """ part of '${Path.basename(buildStep.inputId.path)}'; $channelHelperStr """; } Code _generatorSingleInstantBody() { final blocks = <Code>[]; blocks.add(Code("if(_$className == null) {")); blocks .add(Code("_$className = ${className}Imp._internal() as $className;" )); blocks.add(Code("}")); blocks.add(Code("return _$className;" )); return Block.of(blocks); } // ignore: missing_return Code _generatorBody( MethodElement methodElement, List<ParameterElement> parameters) { final blocks = <Code>[]; if (parameters == null || parameters.length == 0) { blocks.add(Code( "dynamic _result = await $channelName.invokeMethod('${methodElement.name}');" )); } blocks.add(_generatorResult(methodElement)); return Block.of(blocks); } Code _generatorResult(MethodElement methodElement) { final blocks = <Code>[]; return Block.of(blocks); }}Copy the code

4. Create the dart file channel_help_builder in the generate package

This package is used to load ChannelHelpGenerator, specifying the generated file suffix, as follows:

import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart';
import 'channel_help_generator.dart';

Builder nativeCallBuilder(BuilderOptions options) =>
    LibraryBuilder(ChannelHelpGenerator(), generatedExtension: '.nc.g.dart');
Copy the code

5. Create the build.yaml file in the generate package

The code is as follows:

Builders: channel_help_Builder: target: ": Annotations "# Builder_factories: ['nativeCallBuilder'] build_extensions: {'.dart': ['.nc.g.art ']} Auto_apply: dependents # Apply this Builder to the package directly dependent on the package built publicly. Source code # output to annotate the target class with directory, or output to the hidden cache build, does not release (cache) applies_builders: [" source_gen | combining_builder "] # specifies whether can run delay buildersCopy the code

How to use

1. Add dependency Annotations, generate, build_runner to pubSpec. yaml of the main project

The code is as follows:

name: generate_code description: A new Flutter application. publish_to: 'None' # Remove this line if you wish to publish to pub. Dev version: 1.0.0+1 environment: SDK: ">=2.7.0 <3.0.0" ^1.0.0 dev_dependencies: flutter_test: SDK generate: path: generate build_runner: ^1.4.0 uses-material-design: trueCopy the code

2. Create the ChannelTest file and add the ChannelHelp annotation

The following code

import 'package:annotations/channel_help.dart';
import 'package:flutter/services.dart';

part 'channel_test.nc.g.dart'; 


@ChannelHelp('test')
abstract class ChannelTest {

  void test();

}

Copy the code

3. Execute the following commands

flutter packages pub run build_runner build

4. Wait until the instruction is completed to see the compiled code

The code is as follows:

part of 'channel_test.dart';

class ChannelTestImp implements ChannelTest {
  ChannelTestImp._internal();

  final MethodChannel _MethodChannel = MethodChannel("test");

  static ChannelTest _ChannelTest;

  static ChannelTest getInstance() {
    if (_ChannelTest == null) {
      _ChannelTest = ChannelTestImp._internal() as ChannelTest;
    }
    return _ChannelTest;
  }

  @override
  void test() async {
    dynamic _result = await _MethodChannel.invokeMethod('test');
  }
}

Copy the code

QQ communication group of

Group number: 770892444