Flutter:Freezed パッケージをインストール・設定・使用する
July 20, 2022
パッケージとドキュメント
- freezed | Dart Package
- rrousselGit/freezed: Code generation for immutable classes
Freezed とは #
Code generation for immutable classes
イミュータブルなデータクラスを作成するためのコードジェネレータです。
データクラスを用意しようとすると数十行のコードを書く必要がありますが、それを毎回手書きしていては大変ですし、タイポによるバグも起きかねません。
データクラスで書くべきコードは定型のボイラープレートのため、それならば自動生成してしまったほうが早く、かつ間違いもないということで、そのためのコードジェネレータが Freezed です。
具体的には、データクラスのもとになるわずかな量のコードを書いた後にコマンドを叩くと、数十行のデータクラスが書かれたファイルが自動生成されるというものです。
このように Freezed はあくまでも簡略化のためのツールであり、どのプロジェクトでも導入が必須というわけではありません。しかしながらその利便性から多くのユーザに使用されています。
補足:データクラスとは #
主にデータの保持することだけが目的のクラスのこと。フィールドの他に、いくつかの基本的なメソッド(*)を持つだけのクラス。
* equals()
, hashCode()
, toString()
, copy()
などの類
1. インストールとリント設定の編集 #
以降の内容は公式の説明沿っていますが、一部カスタマイズしている部分があります。
1-1. インストール #
flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed
データクラスに fromJson/toJson
メソッドも持たせるのであれば以下も合わせてインストール。
flutter pub add json_annotation
flutter pub add --dev json_serializable
(以降の手順では上記もインストールした前提で進める)
インストールしたパッケージの一覧 #
- https://pub.dev/packages/build_runner
- The tool to run code-generators.
- https://pub.dev/packages/freezed
- The code generator of data-classes/unions/pattern-matching/cloning.
- https://pub.dev/packages/freezed_annotation
- Annotations for freezed.
- https://pub.dev/packages/json_serializable
- The code generator to generate to/from JSON code for a class.
- https://pub.dev/packages/json_annotation
- Annotations for json_serializable.
1-2. リントの設定を編集 #
If you plan on using Freezed in combination with
json_serializable
, recent versions ofjson_serializable
andmeta
may require you to disable theinvalid_annotation_target
warning.To do that, you can add the following to an
analysis_options.yaml
at the root of your project:
analysis_options.yaml
に以下を追記します。
analyzer:
errors:
invalid_annotation_target: ignore
ここまででインストールと初期設定は完了です。
2. Freezed でデータクラスを作成する #
新規でデータクラスを作成するときは以降の手順を踏みます。
2-1. データクラスのもとを作成する #
lib
ディレクトリ直下に model
ディレクトリを作成し、その中に person.dart
ファイルを作成します。
なお、Freezed のファイルは lib
ディレクトリ配下のどこに作成しても問題ありません。専用のフォルダを作成したのは分かりやすさのためです。
そしてperson.dart
ファイルに以下を記載します。
// This file is "lib/model/person.dart"
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
// required: associates our `person.dart` with the code generated by Freezed
part 'person.freezed.dart';
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'person.g.dart';
@freezed
class Person with _$Person {
const factory Person({
required String firstName,
required String lastName,
required int age,
}) = _Person;
factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}
この時点では IDE が何かしらエラーを表示していると思いますが、次の作業で解消されますのでこのまま進めてください。
なお上記コードが意味するところについて、公式ドキュメントで以下の通り説明されています。
The following snippet defines a model named Person:
Person
has 3 properties:firstName
,lastName
andage
- Because we are using
@freezed
, all of this class’s properties are immutable.- Since we defined a
fromJson
, this class is de/serializable. Freezed will add atoJson
method for us.- Freezed will also automatically generate:
- a
copyWith
method, for cloning the object with different properties- a
toString
override listing all the properties of the object- an
operator
== andhashCode
override (sincePerson
is immutable)From this example, we can notice a few things:
It is necessary to annotate our model with
@freezed
(or@Freezed
/@unfreezed
, more about that later). This annotation is what tells Freezed to generate code for that class.We must also apply a mixin with the name of our class, prefixed by
_$
. This mixin is what defines the various properties/methods of our object.When defining a constructor in a Freezed class, we should use the
factory
keyword as showcased (const
is optional). The parameters of this constructor will be the list of all properties that this class contains. Parameters don’t have to be named and required. Feel free to use positional optional parameters if you want!
補足 #
上記は immutable なデータクラスのもとになるものでした。
このほかにも公式ドキュメントでは例えば次のような内容も説明されています。分かりやすく記載されていますので、困ったときには公式ドキュメントを参照するのが良いです。
- (immutable ではなく)mutable なデータクラスを生成するときの書き方
- データクラスに独自のメソッドを持たせたいときの書き方
2-2. build_runner
を実行する
#
ターミナルにて以下のコマンドを実行します。
flutter packages pub run build_runner build --delete-conflicting-outputs
すると、person.dart
の隣に person.freezed.dart
および person.g.dart
が自動生成されており、次のようにファイルが配置されているはずです。
person.dart
person.freezed.dart
person.g.dart
補足:build_runner
による自動再生成
#
watch
でコマンドを実行すると build_runner
が監視状態となり、ファイルに変更を加えるとそれを自動検知してデータクラスを再生成してくれます。
flutter pub run build_runner watch --delete-conflicting-outputs
3. Freezed で生成されたデータクラスを使用する #
試しに main.dart
の main()
の中で確認をしてみると以下になります。
import 'model/person.dart';
void main() {
// 確認ここから
const jane = Person(firstName: 'Jane', lastName: 'Doe', age: 20);
print(jane.firstName); // Jane
print(jane.age); // 20
print(jane.toString()); // Person(firstName: Jane, lastName: Doe, age: 20)
print(jane.toJson()); // {firstName: Jane, lastName: Doe, age: 20}
final agedJane = jane.copyWith(age: 80);
print(agedJane.firstName); // Jane
print(agedJane.age); // 80
print(jane == agedJane); // false
// 確認ここまで
runApp(const MyApp());
}
ここまでで基本的なフローは以上です。
以降はおまけで Tips をご紹介します。
4. Freezed と json_serializable で生成されるファイルの場所を変える #
開発が進み Freezed で生成するファイルが多くなってくると model
ディレクトリの中身がごちゃつきがちです。
その対策として、自動生成されるファイルの作成先を変更してみたいと思います。
まずはプロジェクト直下に build.yaml
というファイルを作成し、そのファイルに下記を記述してください。
targets:
$default:
builders:
freezed:
options:
build_extensions:
'lib/{{dir}}/{{file}}.dart': 'lib/{{dir}}/generated/{{file}}.freezed.dart'
source_gen|combining_builder:
options:
build_extensions:
'lib/{{dir}}/{{file}}.dart': 'lib/{{dir}}/generated/{{file}}.g.dart'
その後 source_gen
というパッケージを追加でインストールします。
flutter pub add --dev source_gen
そして lib/model/person.dart
の part
部分を更新します。
// This file is "lib/model/person.dart"
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
// required: associates our `person.dart` with the code generated by Freezed
part 'generated/person.freezed.dart'; // 👈 変更する。変更前は 'person.freezed.dart' だった。
// optional: Since our Person class is serializable, we must add this line.
// But if Person was not serializable, we could skip it.
part 'generated/person.g.dart'; // 👈 変更する。変更前は 'person.g.dart' だった。
@freezed
class Person with _$Person {
const factory Person({
required String firstName,
required String lastName,
required int age,
}) = _Person;
factory Person.fromJson(Map<String, Object?> json) => _$PersonFromJson(json);
}
そして build_runner
のコマンドを実行すると、今度は以下のようにファイルが作成されるはずです。
lib/
└ model/
├ person.dart
└ generated/
├ person.freezed.dart
└ person.g.dart
こうすることで model
ディレクトリ直下が整理され、見通しが良くなりました。
参考:build.yaml
の設定について
#
https://github.com/dart-lang/build/blob/master/docs/writing_a_builder.md#configuring-outputs
補足:IDE の設定で非表示にすることもできる #
生成されるファイルの出力先を変える他にも、IDE の設定からファイルを非表示にすることで、見かけ上のファイルを消すことができます。
例えば VS Code の settings.json
にて files.exclude
という設定を追加すれば Freezed と json_serializable で生成されたファイルを非表示にすることが可能です。
特定のプロジェクトだけで非表示にしたい場合は、プロジェクトの直下に以下のようにファイルを作成しましょう。
.vscode/settings.json
{
"files.exclude": {
"**/*.freezed.dart": true,
"**/*.g.dart": true
}
}
5. Freezed と json_serializable のビルド速度を改善する #
ファイル数の増加に伴ってビルドに時間がかかるようになりますが、ビルド対象のファイルの場所を指定してあげることで、ビルドの速度が大幅に改善されます。
build.yaml
を以下のように変更します。
targets:
$default:
builders:
freezed:
generate_for:
include:
- lib/model/*.dart
options:
build_extensions:
'lib/{{dir}}/{{file}}.dart': 'lib/{{dir}}/generated/{{file}}.freezed.dart'
json_serializable:
generate_for:
include:
- lib/model/*.dart
source_gen|combining_builder:
options:
build_extensions:
'lib/{{dir}}/{{file}}.dart': 'lib/{{dir}}/generated/{{file}}.g.dart'