理解dart的代码生成工具

声明:本篇文字只是个人的理解,记录,由于水平有限理解粗浅 仅做参考

背景介绍

在flutter的项目开发中 需要在多个函数中 调用重复的代码,例如:

Widget widgetBtn() {
...
return PermissionWidget('abc', child: child);

}

虽然已经 就行了封装,但是还是不够优美(逼格问题),能否像使用json_serializable一样 只需要在coding时 开启build_runner watch,直接调用 已经自动生成的代码;
达到最终效果:

@TallyWidget("abc")
Widget widgetBtn() {
...
return widgetBtn_abc(child:child);
}

在同级目录下生成xx.t.dart文件

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// Generator: WidgetWrapGen
// **************************************************************************

import 'package:flutter/material.dart';
import 'package:z_tally/permission/PermissionWidget.dart';

// ignore: non_constant_identifier_names
Widget widgetBtn_abc({Widget child, WidgetBuilder builder}) {
  return PermissionWidget('abc', child: child, builder: builder);
}

同时 理解掌握flutter 自动生成代码的机制;

先了解一下json_serializable

之所以写下这篇文字主要还是因为在开发中 使用这两个开源库,很方便的自动生成模版代码;最近节奏慢了下来,做个人学习总结;
json_serializable 是pub仓库上比较著名的一个用来json解析的代码生成库,只是这样

part 'StationModel.g.dart';
@JsonSerializable()
class StationModel extends Object {
  @JsonKey(name: 'address')
  String address;
  ...
}

在类声明的同级位置添加了这样 一个引入外部文件的声明,然后执行了这个命令

flutter pub run build_runner build

就在同级的文件下生成了一个新的文件,很是奇怪;Android中也有注解的概念,如果要自动生成代码 通常会定义注解处理器,在编译过程中扫描指定的注解,读取代码的元素信息(注释,注解,code声明)然后生成代码,dart显然也是类似的

Provides source_gen Generators to create code for JSON serialization and deserialization.

这是json_serializable 官方的介绍,基于source_gen

build_runner和source_gen

build_runner build_runner 提供了编译dart代码的命令,而且是pub命令之外的(只是通过pub命令执行build_runner命令) 是个工具脚本,用来执行dart 代码 通过pub 命令执行。通常结合source_gen使用,(是一个用dart编写的库 用来生成dart代码)
使用source_gen创建builder代码构建器,然后build_runner 可选择性的进行配置builder代码构建器,最后执行build_runner命令 执行代码构建器 自动生成dart代码。
(builder代码构建器绑定了gennerator代码生成器,定义了代码生成规则)

Standalone generator and watcher for Dart using package:build.
...
The build_runner package provides a concrete way of generating files using Dart code, outside of tools like pub

大部分情况下 build_runner 不需要单独配置,如果需要自定义配置 就在项目顶级目录下 创建一个build.yaml文件 声明即可;

builder_runner包含在build库中,但是没有被flutter环境默认引入,如果使用需要手动引入;

为什么使用source_gen

source_gen 提供了简化的api 来自动生成代码;

source_gen provides an API and tooling that is easily usable on top of build to make common tasks easier and more developer friendly. For example the PartBuilder class wraps one or more Generator instances to make a Builder which creates part of files, while the LibraryBuilder class wraps a single Generator to make a Builder which creates Dart library files.

source_gen 提供了两种代码构建器:PartBuilder 和SharedPartBuilder

  • PartBuilder 用来生成part of文件
  • SharedPartBuilder 用来生成单独的dart文件

具体参照官方demo
需要注意的是 构建器builder 可指定作用域范围,也在build.yaml文件中配置

# Read about `build.yaml` at https://pub.dev/packages/build_config
targets:
  $default:
    builders:
      # Configure the builder `pkg_name|builder_name`
      # In this case, the member_count builder defined in `../example`
      #-----------指定 代码构建器的 目标作用域
      source_gen_example|member_count:
        # Only run this builder on the specified input.
        #---------指定 代码构建器的 目标文件
        generate_for:
          #- lib/library_source.dart
          - lib/*.dart

      source_gen_example|property_sum:
        generate_for:
          - lib/*.dart
      source_gen_example|property_product:
        generate_for:
          - lib/*.dart
      # The end-user of a builder which applies "source_gen|combining_builder"
      # may configure the builder to ignore specific lints for their project
      source_gen|combining_builder:
        options:
          ignore_for_file:
            - lint_a
            - lint_b

实现方案

创建一个flutter lib项目 t_permission,这个项目引入source_gen;
定义注解类;

class TWidget {
  final String permissionCode;

  const TWidget(this.permissionCode);
}

定义dart代码 的builder(构建器)

Builder TPermissionBuilder(BuilderOptions options) {
  return LibraryBuilder(WidgetWrapGen(),
      generatedExtension: '.tally.dart');
}

定义dart代码 的Generator(生成器)


class WidgetWrapGen extends GeneratorForAnnotation<TallyPermission> {

  @override
  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) {
    return super.generate(library, buildStep);
  }

  @override
  String generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    print("------------------- element is $element , runtimeType is ${element.runtimeType}");
    if (element is! ClassElement) {
      throw '-- TPermission only support Class !!';
    }
    ClassElement e = element as ClassElement;
    StringBuffer stringBuffer = StringBuffer();
    bool first=true;
    e.methods.forEach((m) {
      String methodName = m.name;
      m.metadata.forEach((an) {
        DartObject metadata = an.computeConstantValue();
        ParameterizedType metadataType = metadata.type;
        String metadataTypeName = metadataType.getDisplayString();
        if (metadataTypeName == 'TWidget') {
          String permissionCode = metadata.getField('permissionCode').toStringValue();
          if(first){
            first=false;
            stringBuffer.write('''
            import 'package:flutter/material.dart';
            import 'package:zh/permission/PermissionWidget.dart';
          
          ''');
          }
          stringBuffer.write('''
          
          // ignore: non_constant_identifier_names
          Widget ${methodName}_$permissionCode({Widget child, WidgetBuilder builder}){
            return PermissionWidget('$permissionCode',child:child,builder:builder);
          }
          
          ''');
        }
      });
    });
  //返回 生成的代码内容
    return stringBuffer.toString();
  }

}

项目 引入依赖 t_permission;
在需要使用PermissionWidget的函数位置,指定注解;

@TallyWidget("abc")
  Widget widgetBtn() {
  widgetBtn_abc(child:child);
...

运行 flutter packages pub run build_runner build 命令;

更多例子:
build_runner
构建RequestUtil
mixiaodou/source_gen

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342