[Flutter/Dart] Freezed 3.0 がリリース! 2.x からの主要な変更点まとめ
最近(といっても2ヶ月位前っぽい)リリースされた Freezed 3.0 について、 2.x からの更新作業にあたって主要な変更点を調べてまとめました。[1]
破壊的変更
sealed または abstract の必須化
factory コンストラクタを用いるクラスには abstract (単一クラスの場合) または sealed (直和型(Union)の場合) が必要になりました。
@freezed
-class Person with _$Person {
+abstract 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);
}
@freezed
-class Model with _$Model {
+sealed class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
map when と、その派生メソッドが削除
Dart にパターンマッチングが導入されたことにより非推奨となっていた map や when などのメソッドが削除されました。
解決策
Dart のパターンマッチングを用いるように書き換えてください。
final model = Model.first('42');
-final res = model.map(
- first: (String a) => 'first $a',
- second: (int b, bool c) => 'second $b $c',
-);
+final res = switch (model) {
+ First(:final a) => 'first $a',
+ Second(:final b, :final c) => 'second $b $c',
+};
新規要素
Mixed mode
mixed mode という定義方法ができるようになりました。
従来の factory コンストラクタを用いた定義方法では、 Freezed がコンストラクタやプロパティを生成していました。
mixed mode では、コンストラクタやプロパティは開発者が定義することができます。
これまでの定義の仕方 (Factory コンストラクタ)
@freezed
sealed class Usual with _$Usual {
// Freezed がコンストラクタとプロパティを生成
factory Usual({int? a}) = _Usual;
}
3.0 からできる書き方 (通常のコンストラクタ)
@freezed
class Usual with _$Usual {
// 開発者がコンストラクタと final プロパティを定義
Usual({this.a});
final int? a;
}
これにはいくつかのメリットがあって、
- シンプルなデータクラスであれば、(「Freeezed しぐさ」な)
factoryを使わずに通常のコンストラクタでプロパティを定義できます。 - 直和型(Union)はこれまでどおり
factoryを利用して定義できます。
また、この mixed mode により、通常のコンストラクタが利用可能になったことで、以下のような super コンストラクタの呼び出しも可能になりました。
class Base {
Base(String value);
}
@freezed
class Usual extends Base with _$Usual {
// 通常のコンストラクタで super を呼び出す
Usual({int? a}) : a = a ?? 0, super('value');
final int a;
}
Inheritance and non-constant default values (継承と非定数デフォルト値)
従来の Freezed ではクラスの継承 (extends) や、コンストラクタ引数に対する非定数 (const でない) のデフォルト値の設定に制限がありました。
Freezed 3.0 では、mixed mode とプライベートコンストラクタ (クラス名._()) を組み合わせることで、これらの機能が利用可能になりました。
従来、Freezed クラスに独自のメソッドやプロパティを定義するには、以下のように空のプライベートコンストラクタ クラス名._(); を定義する必要がありました。
しかし、このプライベートコンストラクタ ._() にパラメータを渡すことはできませんでした。
@freezed
class Example with _$Example {
Example._(); // helloWorld() が動作するために必要
factory Example(String name) = _Example;
void helloWorld() => print('Hello $name');
}
Freezed 3.0 では、このプライベートコンストラクタに任意のパラメータを渡すことができます。Freezed はパラメータ名に基づいて、他の factory コンストラクタから値を受け渡します。これにより、super の呼び出しや非定数デフォルト値の設定が可能になります。
継承 (extends) の例:
class Subclass {
Subclass.name(this.value);
final int value;
}
@freezed
class MyFreezedClass extends Subclass with _$MyFreezedClass {
// プライベートコンストラクタ ._ でパラメータを受け取り、
// super.name() を呼び出して基底クラス (Subclass) のコンストラクタに値を渡す
MyFreezedClass._(super.value): super.name();
// Freezed は value フィールドを MyFreezedClass._ に渡すようにコード生成する
factory MyFreezedClass(int value, /* other fields */) = _MyFreezedClass;
}
以下のように、定数でないデフォルト値も利用できます。
@freezed
sealed class Response<T> with _$Response<T> {
// プライベートコンストラクタ ._ 内で、time パラメータに非定数のデフォルト値 DateTime.now() を設定
Response._({DateTime? time}) : time = time ?? DateTime.now();
// 各 factory コンストラクタは、必要に応じてプライベートコンストラクタ ._() に値を渡せる
// time を指定しない場合、._() 内のデフォルト値が使われる
factory Response.data(T value, {DateTime? time}) = ResponseData;
// プライベートコンストラクタ ._() のパラメータが名前付きでオプショナルな場合 ({DateTime? time})、
// factory コンストラクタで明示的に指定しない限り、._() 内で設定されたデフォルト値が使用される
factory Response.error(Object error) = ResponseError;
@override
final DateTime time;
}
"Eject" union cases (特定の Union ケースの分離)
直和型 (Union types) の特定のケースについて、Freezed によるコード生成を行わず、独自に定義したクラスを使用できるようになりました。
@freezed
sealed class Result<T> with _$Result {
Result._();
// ResultData は未定義なので、従来通り Freezed が自動生成します
factory Result.data(T data) = ResultData;
// 右辺に既存のクラス名 (ResultError) を指定すると、Freezed は
// そのケース (ResultError) のコード生成を行わず、指定されたクラスをそのまま利用します
factory Result.error(Object error) = ResultError;
}
// 独自に定義した ResultError クラス
// sealed クラスである Result<T> を extends する必要があります
class ResultError<T> extends Result<T> {
ResultError(this.error): super._();
final Object error;
}
この独自定義クラス (ResultError) も Freezed クラスにすることができ、前述の mixed mode と組み合わせることができます。
// mixed mode を用いたシンプルなクラス
@freezed
class ResultError<T> extends Result<T> with _$ResultError<T> {
ResultError(this.error): super._();
final Object error;
}
// または、 factory を用いる書き方も可能
@freezed
abstract class ResultError<T> extends Result<T> with _$ResultError<T> {
ResultError._(): super._();
factory ResultError(Object error) = _ResultError;
}
その他の変更
// dart format off の自動挿入
Freezed の設定でフォーマットが無効 (format: false、これがデフォルト) の場合、生成される .freezed.dart ファイルの先頭に // dart format off が付与されるようになりました。
これにより、生成されたファイルを CI のフォーマットチェックから除外する設定が不要になる場合があります。
Union ケースのプライベート化
直和型 (Union types) の各ケースに対応する factory コンストラクタをプライベート (_) にできるようになりました。
@freezed
sealed class Result<T> with _$Result {
// この ._data() は 3.0 から書けるようになった
factory Result._data(T data) = ResultData;
factory Result.error(Object error) = ResultError;
}
参考資料
- 3.0 CHANGELOG: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/CHANGELOG.md#300---2025-02-25
- Migrate from v2 to v3: https://github.com/rrousselGit/freezed/blob/master/packages/freezed/migration_guide.md
-
だいたい公式の CHANGELOG / Migration Guide の翻訳です。一部ファイル生成時にエラーになるコードを修正しています。 ↩︎
Discussion