flutterのfreezedを外部ライブラリにも使いたい

3 min read読了の目安(約2900字

そう思ったことはありませんか?私はあります。

こんなコードを想像してみましょう。

import 'package:some_3rd_party_lib/some_3rd_party_lib.dart';

void main() {
  final res = somethingOf3rdPartyLib();
  println(res); // output Instance of `3rdPartyLibClass`
}

外部のライブラリはおそらくpure dartなclassで実装されたものをレスポンスとして返してくれます。
しかし、このレスポンスをdebug printしてもfreezedの様にclassの中身をいい感じに出力してはくれません。

もちろん外部のライブラリのclassを確認して通常通りfreezed用のボイラープレートを書けばそれで済む話ではあります。

part of 'extended_3rd_party_lib.dart';


abstract class Freezed3rdPartyLibClass implements _$Freezed3rdPartyLibClass {
  const Freezed3rdPartyLibClass._();
  factory Freezed3rdPartyLibClass({
    String id,
    int number,
    String description,
  }) = _Freezed3rdPartyLibClass;

  factory Freezed3rdPartyLibClass._$From(Todo self) => Freezed3rdPartyLibClass(
        id: self.id,
        number: self.number,
        description: self.description,
      );
}

extensionも作っておけばフィールドをわざわざ指定しなくても済むのでいいかもしれません。

extension Extended3rdPartyLibClass on 3rdPartyLibClass {
  FreezedTodo freeze() => Freezed3rdPartyLibClass._$From(this);
}

しかしこの方法だと外部のライブラリが更新されてclassの構造が変わった場合に追従する手間が発生します。

そこでこれを自動で行ってくれるライブラリを作りました。

https://pub.dev/packages/freezify

https://pub.dev/packages/freezify_annotation

freezifyです。

使い方は至って簡単で、適当なファイルにextensionを定義してfreezifyのannotationを指定します。

import 'package:some_3rd_party_lib/some_3rd_party_lib.dart';

// 必須
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:freezify_annotation/freezify_annotation.dart';

part 'extended_3rd_party_lib.freezed.dart';
part "extended_3rd_party_lib.freezify.dart";


extension ExtendedTodo on Todo {
  FreezedTodo freeze() => FreezedTodo._$From(this);
}

これだけです。

あとはfreezedでお馴染みのbuild_runnerを走らせてあげればfeezifyから自動でfreezedのボイラープレートが生成され、それを元にfreezedのコード生成を行ってくれます。

やってることは至ってシンプル、を通り越して雑で、code_genのタイミングで以下のようなテンプレートでfreezedのボイラープレートを生成しているだけです。


abstract class Freezed${_extendedType} implements _\$Freezed${_extendedType} {
  const Freezed${_extendedType}._();
  factory Freezed${_extendedType}({
    ${_fields}
  }) = _Freezed${_extendedType};
  factory Freezed${_extendedType}._\$From(${_extendedType} self) => Freezed${_extendedType}(
      ${_fieldArgs});
}
builders:
  freezify:
    import: "package:freezify/builder.dart"
    builder_factories: ["freezify"]
    build_extensions: { ".dart": [".freezify.dart"] }
    auto_apply: dependents
    build_to: source
    runs_before: ["freezed:freezed", "json_serializable|json_serializable"]

https://github.com/azihsoyn/freezify/blob/main/packages/freezify/build.yaml

builderのコードは生成するコードの依存関係を指定できるのでfreezify -> freezedの順でコード生成することで今回の仕組みを実現しています(freezedもjson_serializableの前に実行するように設定されています)

便利ライブラリとして紹介しましたが、現時点では個人用に作ったのでテストもドキュメントもなく、そろそろリリースされるであろうnull-safety版での動作確認も出来ていません(自分の環境では問題なく動いていますが)。

これを公開することで「freezedだけでもできるよ」とか「こんなライブラリもあるよ」みたいな反応があるといいなと思い書きました。

コード生成のツールも一度作ってみたかったので挑戦してみてよかったです。

眠いので以上です。