【Flutter】freezedをサクッと理解。基本のまとめとシンプルな実装例
flutterパーケージのfreezedを使用するケースは現場や個人開発で数多くあると思います。
今回はfreezedについてシンプルな理解をまとめました。
理解を深めたい/あまり知らないアプリエンジニアに参考になれば幸いです。
freededとは
-
データクラスとそれに必要な機能を自動生成してくれるパッケージ
- データクラス(普遍クラス)の生成
- copyWith,toStringのの自動生成
それぞれ深掘り
機能 | 説明 | 目的(補足) |
---|---|---|
イミュータブル | 作った後に中身を直接書き換えられない、安全性高い | 不変性を保ち、バグを減らす |
copyWith | 部分的に値を変えた「新しい」インスタンスを作る | 状態管理や部分更新に便利 |
toString | デバッグ時などに中身を見やすい文字列にする | ログ出力やデバッグの効率化 |
公式
モチベーション
- 可読性の向上
- toStringやoperator ==など毎度書くのを省略
- 安全性の担保
- イミュータブルなデータ構造を簡単に扱え、バグの発生リスクを下げる
使用シーン
- オリジナルの型を使用したい場合
- データベースから取得した型を変換し安全にコードに落とし込む
使用例
パッケージのインストール
公式記載にある通り、下記3つのパッケージをインストールします。
This installs three packages:
- build_runner, the tool to run code-generators
- freezed, the code generator
- freezed_annotation, a package containing annotations for freezed.
pubspec.yamlになるべく最新バージョンで定義することをおすすめします。
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^2.2.0
dev_dependencies:
flutter_test:
sdk: flutter
freezed: ^2.4.2
build_runner: ^2.3.3
freededの書き方全体
以下のようなケースを想定したシンプルな型変換です。
- firebaseからデータをもらうが自分の型を作りたい
- イベント名, イベントの時間, ユーザーIDと一般的なデータ
import 'package:freezed_annotation/freezed_annotation.dart';
part 'event_model.freezed.dart';
part 'event_model.g.dart';
abstract class EventModel with _$EventModel {
const factory EventModel({
required String event,
required DateTime date,
required String userid,
}) = _EventModel;
factory EventModel.fromJson(Map<String, dynamic> json) => _$EventModelFromJson(json);
}
freededの宣言
この辺りはfreededのお作法でこう書くぐらいの理解で良いかなと思います。
二つのpartするファイルの違いは以下の通りです。
-
.freezed.dart
: モデル本体の自動生成(copyWith, ==, hashCode, etc.) -
.g.dart
: JSON との変換を担当(fromJson/toJson など)
import 'package:freezed_annotation/freezed_annotation.dart';
part 'event_model.freezed.dart';
part 'event_model.g.dart';
freededの型定義
with _$EventModel {
. }) = _EventModel
については同じくお作法です。
各要素の意味としては以下になります。
-
const factory
: イミュータブル(不変)なインスタンスを作るため の特殊な構文 -
{}
内の項目は「イベント名」「日時」「ユーザーID」の3つ -
required
: 必須項目であることを表す
abstract class EventModel with _$EventModel {
const factory EventModel({
required String event,
required DateTime date,
required String userid,
}) = _EventModel;
factoryについて深く理解したい。という方はこちらの記事もご覧ください。
注意として、freezed
では「abstract class」を使わない書き方(@freezed
+ class EventModel)も可能です。
Json形式からの変換
- firebaseなどjsonデータで受け取った場合に型を独自に変換
- 以下のようなfromJsonメソッドを定義してあげる
factory EventModel.fromJson(Map<String, dynamic> json) => _$EventModelFromJson(json);
上記のメリットとしては、「Firestore や API から取ってきた JSON を Dart モデルに安全に変換できる」ことです。
build runnerの実行
freededを宣言しているファイルを定義できればあとはbuild runnerを実行すればokです。
ターミナルで以下のコマンドを実行します。
flutter pub run build_runner build
コンフリクトするファイルなどを削除し再生成を自動でしたいなら下記コマンドを実施
flutter pub run build_runner build --delete-conflicting-outputs
実際のデータ処理に自分のモデルを使用する
今回のケースは以下の想定です。
- firebaseからデータを取得し変換
- 変換したものをData層から別の層に渡す
final model = EventModel.fromJson(data);
の部分でfreededファイルで定義したものを使用しています。
/// Firestore からイベントを取得し、日付ごとにまとめる関数
Future<Map<DateTime, List<Map<String, String>>>> fetchEvents() async {
final firebaseEvents = FirebaseFirestore.instance.collection('events');
final snapshot = await firebaseEvents.get();
Map<DateTime, List<Map<String, String>>> events = {};
for (var doc in snapshot.docs) {
final data = doc.data();
final model = EventModel.fromJson(data);
events[model.date] ??= [];
events[model.date]!.add({
'event': model.event,
'userid': model.userid,
});
}
return events;
}
この例では最終的に Map に戻していますが、途中で EventModel
に変換することで、日付の型保証やプロパティ名の補完など、開発中の安全性と可読性が大きく向上します。
fromJson
の役割
final model = EventModel.fromJson(data);
これにより、以下を自動でやってくれます。
- キー名のスペルミス防止
- 型の安全な変換(例えば
date
は DateTime と保証) - テストのしやすさ(手書き Map をモックにできる)
最後に
freededを使いことで下記メリットがあることが理解できます。
- データを安全に保持できる
- すっきりとしたコードで可読性の向上
割とGeneratorで作るとブラックボックスしやすいのでこういった理解する姿勢を持っておくことは意外と大切です。
どなたかの参考になれば幸いです。
Discussion