📌

[Flutter]コード生成したい!その1

2021/07/27に公開

json_serializableやfreezedを使うと便利なコードを自動生成してくれます。
この生成ってどうやっているのか、気になったのでまとめて記事にしてみます。

とりあえずなにか生成したい

次のようなコードを書いたら、test.g.dartファイルが生成したい。

part 'test.g.dart';

()
class A {}

準備

ディレクトリ構造は

dart_code_gen
    |
    +-- hello_code_gen
    +-- example

という構造にしました。
hello_code_genの方にコード生成のためのコードを書いて、exampleで動作確認する感じです。

hello_code_genはflutter create --template=package hello_code_genで生成しました。
exampleはflutter create exampleで生成しました。

hello_code_genのpubspec.yaml

ライブラリはsource_gen, analyzer, build, build_runnerを使います。

...dependencies:
  flutter:
    sdk: flutter
  analyzer:
  build:
  source_gen:

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:

...

アノテーションの定義とコード生成

hello_code_gen.dartにアノテーション自体の定義とアノテーションされた要素に対して、生成を行うコードを書きます。

library hello_code_gen;

import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';

class HelloAnno {
  const HelloAnno();
}

class HelloAnnoGenerator extends GeneratorForAnnotation<HelloAnno> {
  
  generateForAnnotatedElement(
    Element element,
    ConstantReader annotation,
    BuildStep buildStep,
  ) {
    return "class Hello {}";
  }
}

GeneratorForAnnotationのジェネリクスとしてHelloAnnoを渡すことで、@HelloAnnoのようにアノテーションされている要素に対して、コード生成を行うように設定できます。

コードの生成はgenerateForAnnotatedElementメソッドを使用します。引数としてアノテーションされたクラスの情報が渡されるようなのですが、今回は使用しません。返り値としてStringを返すことで、そのStringがコードして生成されます。

今回はどんなクラスにアノテーションされたかに関係なく、class Hello {}というコードを生成します。

builderの定義

build_runnerに登録するために, Builderクラスを返す関数を定義する必要があります。
source_genパッケージではコードの生成タイプに合わせて3種類のBuilderが用意されていますが、今回はSharedPartBuilderを使用します。source_genのREADME

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

import 'hello_code_gen.dart';

Builder helloBuilder(BuilderOptions _) =>
    SharedPartBuilder([HelloAnnoGenerator()], "hello");

build.yamlの設定

build_runnerで生成をしてもらうためにbuild.yamlを記述する必要があります。

builders:
  hello_code_gen:
    import: "package:hello_code_gen/builder.dart"
    builder_factories: ["helloBuilder"]
    build_extensions: {".dart": [".g.dart"]}
    auto_apply: dependents
    applies_builders: ["source_gen|combining_builder"]

importで本パッケージのbuilderを返す関数があるファイルを指定して、 builder_factoriesで関数名を指定しました。

その他のプロパティの詳しい仕様はここに書いてあります。

使ってみる

exampleの方のプロジェクトに移動して、コード生成をしてみます。
その前にpubspec.yamlを開いて、hello_code_genとbuild_runnerを設定します。

dependencies:
  flutter:
    sdk: flutter
  hello_code_gen:
    path: ../hello_code_gen
  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:

test.dartなど適当なファイルに次のコードを書きます。

import 'package:hello_code_gen/hello_code_gen.dart';

part 'test.g.dart';

()
class A {}

ターミナルを開いてflutter pub run build_runner build --delete-conflicting-outputsします。

コード生成が完了すると、test.g.dartに次のコードが生成されるはずです。

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'test.dart';

// **************************************************************************
// HelloAnnoGenerator
// **************************************************************************

class Hello {}

まとめ

とりあえず生成できました。
次は, generateForAnnotatedElementメソッドの引数で情報を受け取って、なにかしら生成を変更するようにしてみたいです。

今回のコード

Discussion