flutter 使用source_gen和code_builder編譯時(shí)生成代碼

前言

以前學(xué)android的時(shí)候就曾經(jīng)使用過(guò)APT在編譯時(shí)解析注解颠黎,然后結(jié)合Javapoet生成代碼捏卓,在flutter端其實(shí)也有這種類(lèi)似的技術(shù)惑畴,這就是source_gen和code_builder诱渤,source_gen負(fù)責(zé)編譯時(shí)解析注解蚌铜,code_builder負(fù)責(zé)生成代碼盯另,本篇文章性含,就是在講解如何使用source_gen和code_builder編譯時(shí)生成代碼,code_builder如何使用鸳惯,請(qǐng)看之前的文章, 本文 github鏈接

具體步驟

1. 新建flutter package商蕴,命名為annotations

這個(gè)包專(zhuān)門(mén)用來(lái)放注解,在這個(gè)包中新建一個(gè)注解類(lèi)芝发,如下所示

class ChannelHelp {
  final String channelName;
  const ChannelHelp(this.channelName); //注解類(lèi)绪商,構(gòu)造函數(shù)必須是Const的
}

2. 新建flutter package,命名為generate

這個(gè)包專(zhuān)門(mén)用來(lái)解析注解辅鲸,生成代碼格郁,先在pubspec.yaml中依賴(lài)上面創(chuàng)建的包annotations,然后再添加依賴(lài)source_gen和build_runner独悴,代碼如下

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"

dependencies:
  flutter:
    sdk: flutter
  source_gen: ^0.9.6 #需要包含代碼自動(dòng)庫(kù)
  annotations:
    path: ../annotations

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.10.0

flutter:

3.在generate包中新建dart類(lèi)ChannelHelpGenerator

這個(gè)類(lèi)繼承自ChannelHelpGenerator例书,用于解析ChannelHelp注解,代碼如下:

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只能用在類(lèi)上');
    }
    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.add(TypeReference((build) {
              // 給方法添加注解
              build.symbol = "override"; //注解類(lèi)型是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);
  }
}

4.在generate包中新建dart文件channel_help_builder

這個(gè)包用于加載ChannelHelpGenerator刻炒,指定生成的文件后綴决采,代碼如下:

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');

5.在generate包中新建文件build.yaml

代碼如下:

builders:
  channel_help_builder:
    target: ":annotations" #目標(biāo)庫(kù)
    import: 'package:generate/channel_help_builder.dart'  #build文件
    builder_factories: ['nativeCallBuilder']
    build_extensions: {'.dart': ['.nc.g.dart']}
    auto_apply: dependents #將此Builder應(yīng)用于包,直接依賴(lài)于公開(kāi)構(gòu)建起的包坟奥,也可以是root_package
    build_to: source #輸出到注解的目標(biāo)類(lèi)的代碼同目錄中树瞭,或者輸出轉(zhuǎn)到隱藏的構(gòu)建緩存,不會(huì)發(fā)布(cache)
    applies_builders: ["source_gen|combining_builder"] #指定是否可以延遲運(yùn)行構(gòu)建器

如何使用

1. 在主工程的pubspec.yaml添加依賴(lài)annotations爱谁,generate晒喷,build_runner

代碼如下:

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"

dependencies:
  flutter:
    sdk: flutter
  annotations:
    path: annotations
  cupertino_icons: ^1.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  generate:
    path: generate
  build_runner: ^1.4.0

flutter:

  uses-material-design: true

  

2. 新建ChannelTest文件,添加ChannelHelp注解

代碼如下

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

part 'channel_test.nc.g.dart'; 


@ChannelHelp('test')
abstract class ChannelTest {

  void test();

}

3.執(zhí)行指令访敌,如下

flutter packages pub run build_runner build

4.等待指令執(zhí)行完成即可看見(jiàn)編譯生成的代碼

代碼如下:

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');
  }
}

QQ交流群

群號(hào)碼:770892444

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凉敲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爷抓,老刑警劉巖雨效,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異废赞,居然都是意外死亡徽龟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)唉地,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)据悔,“玉大人,你說(shuō)我怎么就攤上這事耘沼〖牵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵群嗤,是天一觀的道長(zhǎng)菠隆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狂秘,這世上最難降的妖魔是什么骇径? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮者春,結(jié)果婚禮上破衔,老公的妹妹穿的比我還像新娘。我一直安慰自己钱烟,他們只是感情好晰筛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著拴袭,像睡著了一般读第。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拥刻,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天怜瞒,我揣著相機(jī)與錄音,去河邊找鬼泰佳。 笑死盼砍,一個(gè)胖子當(dāng)著我的面吹牛尘吗,可吹牛的內(nèi)容都是我干的逝她。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睬捶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼黔宛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起擒贸,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤臀晃,失蹤者是張志新(化名)和其女友劉穎觉渴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體徽惋,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡案淋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了险绘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踢京。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宦棺,靈堂內(nèi)的尸體忽然破棺而出瓣距,到底是詐尸還是另有隱情,我是刑警寧澤代咸,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布蹈丸,位于F島的核電站,受9級(jí)特大地震影響呐芥,放射性物質(zhì)發(fā)生泄漏逻杖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一思瘟、第九天 我趴在偏房一處隱蔽的房頂上張望弧腥。 院中可真熱鬧,春花似錦潮太、人聲如沸管搪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)更鲁。三九已至,卻和暖如春奇钞,著一層夾襖步出監(jiān)牢的瞬間澡为,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工景埃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媒至,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓谷徙,卻偏偏與公主長(zhǎng)得像拒啰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子完慧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容