🦁

【Flutter】freezedをサクッと理解。基本のまとめとシンプルな実装例

に公開

flutterパーケージのfreezedを使用するケースは現場や個人開発で数多くあると思います。
今回はfreezedについてシンプルな理解をまとめました。
理解を深めたい/あまり知らないアプリエンジニアに参考になれば幸いです。

freededとは

  • データクラスとそれに必要な機能を自動生成してくれるパッケージ
    • データクラス(普遍クラス)の生成
    • copyWith,toStringのの自動生成

それぞれ深掘り

機能 説明 目的(補足)
イミュータブル 作った後に中身を直接書き換えられない、安全性高い 不変性を保ち、バグを減らす
copyWith 部分的に値を変えた「新しい」インスタンスを作る 状態管理や部分更新に便利
toString デバッグ時などに中身を見やすい文字列にする ログ出力やデバッグの効率化

公式

https://pub.dev/packages/freezed

https://github.com/rrousselGit/freezed

モチベーション

  • 可読性の向上
    • toStringやoperator ==など毎度書くのを省略
  • 安全性の担保
    • イミュータブルなデータ構造を簡単に扱え、バグの発生リスクを下げる

使用シーン

  • オリジナルの型を使用したい場合
    • データベースから取得した型を変換し安全にコードに落とし込む

使用例

パッケージのインストール

公式記載にある通り、下記3つのパッケージをインストールします。

This installs three packages:

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について深く理解したい。という方はこちらの記事もご覧ください。
https://katsublog.xyz/660/

注意として、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