Flutter でメタデータ(アノテーション)とリフレクション

Flutter でメタデータ(アノテーション)とリフレクション

November 7, 2022

実現したい機能があって、自作パッケージでも作ろうかと調べていたときのメモです。

構想していた機能を実現するためにはメタデータとリフレクションを使用する必要がありました。

ちなみにその後、パッケージを自作することはありませんでした(理由は最後に分かります)😂

メタデータ #

@deprecated とか @override みたいなアレです。

アノテーションだったり、他の言語ではデコレータだったりと呼び方がバラついていますが、Dart 的にはメタデータというみたいです。

としつつもアノテーションと呼んでいる人も一定数いて、検索すると結構な記事が HIT するので、調べ物をするときはあえてアノテーションで検索するのもアリです。

具体的な作り方・使い方は調べれば日本語の説明記事などもすぐに出てくるので詳細は省略します。

といっても constant コンストラクタなクラスを定義すればメタデータとして使用できる、以上!なくらいに単純でありかつ機能性もほぼないです。

基本的に、リフレクションであったり、ソースコードジェネレータと組み合わせて利用することになるはず。

ソースコードジェネレータ #

今回はリフレクションで対応しようとしていたためこちらはスコープ外ですが、メタデータのところで名前を出したので補足として。

こちらの記事がわかりやすいです。

個人的には build_runner を使うのがあまり好きではなく、だからこそリフレクションで機能を実現しようとしていました。(…が、このあとわかりますが、結局 build_runner は使用せざるを得ないです。)

Dart でリフレクション - dart:mirrors #

Dart でリフレクションを行うには dart:mirrors というビルトインライブラリを使用するようです。

私がここで説明するよりも参考になる記事がいくつかあるので貼っておきます。

そして残念ながら Flutter では dart:mirrors が使用できませんので(後述)、そこまで深く読み込まなくても大丈夫です。

Flutter で dart:mirrors は使用不可 #

dart:mirrors ある程度理解したぞとなったときに知った事実、Flutter で dart:mirrors は使えません。

The dart:mirrors package is not available with flutter.

上記スレッド内でもリンクされていますが、詳しくはこちらです。

dart:mirrors が使用できないようになっている理由が説明されています。

Allowing mirrors would mean we couldn’t remove unused code (since all the code is implicitly used). That’s what causes the code size increase.

Java apps are huge, that’s one of the things we want to avoid. Objective C, as I understand it, doesn’t trim unused members from classes either. It also doesn’t strip any classes that are exported, because it uses dynamic linking.

Dart uses static linking (effectively), and using static analysis we can strip out anything that isn’t used (“tree shaking”). If you import a huge Dart library but only use a self-contained two-line method, then you only pay the cost of the two-line method, even if that Dart library itself imports dozens and dozens of other libraries.

要約するとこんな感じです。Flutter はリリースビルド時に不要なコード除去を行いアプリサイズを最適化しています。dart:mirrors でリフレクションを利用することで、暗黙的にすべてのコードが利用される可能性を持ちます。この結果、不要なコードの削除が行えなくなります。これによりアプリサイズが巨大化することから、Flutter においては dart:mirrors の使用が禁止されています。とのこと。

最後の一文には「巨大なライブラリを import していても、実際にはその内の(自己完結している)2メソッドしか使われていないのであれば、その2行分のコストしか支払う必要はない。」と書いてあります。つまり、ビルド時のツリーシェイキングによりここまで最適化しているんですよーと。

ということで、Dart としては使用できる dart:mirrors ですが、Flutter としては使用できません。

Flutter でリフレクション - Reflectable パッケージ #

では、Flutter ではリフレクションが使えないのかというと、回避策になるようなパッケージを Google が作成しています。それが Reflectable パッケージです。

説明は次の通りで、dart:mirrors を使用せずにこれと同じようなことを実現するもの、という感じですね。

Support for generating code that implements a large subset of the features offered by ‘dart:mirrors’ without relying on ‘dart:mirrors’ itself. Provides a system based on capabilities to control the amount of reflection support.

で、この Reflectable は build_runner と一緒に使うことが前提となっています。

ということで、この記事の途中で書いた、

個人的には build_runner を使うのがあまり好きではなく、だからこそリフレクションで機能を実現しようとしていました。(…が、このあとわかりますが、結局 build_runner は使用せざるを得ないです。)

というコメントの回収が完了したわけです。

そして、build_runner を使用するのであれば、自分が欲しい機能をすでに実現しているパッケージはある、ということで、パッケージを自作することはありませんでした 😇