💬

【Flutter】Freezedでデータクラス(モデル)を作成

に公開

はじめに

Flutterでのアプリ開発において、堅牢なデータクラス(モデル)の設計は避けて通れません。しかし、手動で copyWith== のオーバーライド、JSON変換などを実装するのは、非常に手間がかかり、バグの温床にもなりがちです。

そこで登場するのが、今やFlutter開発のデファクトスタンダードとも言えるコード生成ツール「Freezed」です。本記事では、Freezedの基本的な導入方法から、json_serializable と組み合わせた実践的なAPI通信の実装までを、コード例を交えて解説します。

https://pub.dev/packages/freezed

そもそも Freezed とは?

一言で言うと、「データクラス(モデル)を最強にするためのコード生成ツール」です。

Dartで通常のクラスを作ると、以下のような面倒な作業(ボイラープレート)が発生します。

  1. すべてのプロパティに final をつける(不変性)
  2. コンストラクタを書く
  3. toString をオーバーライドする(デバッグ用)
  4. operator ==hashCode をオーバーライドする(値の比較用)
  5. copyWith メソッドを手動で実装する(一部だけ値を変更した複製を作るため)

Freezedを使えば、これら全てを数行のコードを書くだけで自動生成してくれます。

シンプルな利用例

導入

flutter pub add freezed_annotation
flutter pub add json_annotation

flutter pub add --dev freezed
flutter pub add --dev json_serializable
flutter pub add --dev build_runner

pubspec.yaml

dependencies:
  freezed_annotation: ^3.1.0

dev_dependencies:
  json_serializable: ^6.11.2
  build_runner: ^2.10.4
  freezed: ^3.2.3

データモデルファイルを作成

lib/models/data.dart

import 'package:freezed_annotation/freezed_annotation.dart';

// '元ファイル名.freezed.dart'
part 'data.freezed.dart';

// JSON変換(json_serializable)を使う場合
part 'data.g.dart';

// Freezedで生成対象(abstractかseeled)

abstract class Data with _$Data {
  // コンストラクタで定義したプロパティが、生成されるクラスのフィールドになる
  const factory Data({
    required String id, 
    required String title,    
  }) = _Data; // リダイレクトコンストラクタ
              // = _Data; の部分は、ボイラープレート(決まり文句)。

  // JSON(Map)をクラスに変換するためのメソッド
  // これを書くと、freezedが裏側で json_serializable を呼び出す
  factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
  
  // Map<String, dynamic> toJson() => ... は書きません
  // fromJsonを書いた時点で、Freezedが自動的に toJson も生成してくれます
  // 自分で書くと競合してエラーになります。
}

コード生成を実行

dart run build_runner watch

データクラスを使ってみる

インスタンス生成とプロパティ利用例

final newData = Data(id:'999', title:'some_title');
print(newData.id);
print(newData.title);

==で同値比較の例(プロパティの値で比較)

final newData = Data(id:'999', title:'some_title');
print(newData == Data(id:'999', title:'some_title')); // true

copyWithでオブジェクトの一部を更新

final newData = Data(id:'999', title:'some_title');
final updateData = newData.copyWith(id: 'AAA');
print(updateData.id); // AAAに更新されている

通信でJSONのやりとり例(fromJson, toJson)

import 'package:dio/dio.dart';
import 'package:performance_flutter/models/data.dart';

/// DIOインスタンス
final dio = Dio();

/// GETリクエスト
Future<List<Data>> fetchItems() async {
  final res = await dio.get<List>('http://localhost:3000/datas');

  // List<dynamic> -> List<Data>に変換
  final dataList =
      res.data!.map((el) {
        return Data.fromJson(el);
      }).toList();

  return dataList;
}

/// POSTリクエスト
Future<void> postData(Data newData) async {
  final res = await dio.post(
    'http://localhost:3000/datas',
    data: newData.toJson(),
  );
  print(res.data);
}

おわりに

本記事では、Flutter開発における強力なパートナーである「Freezed」について、導入からJSON連携までを解説しました。

改めてFreezedのメリットを振り返ると、以下の点が挙げられます。

  • 圧倒的なコード量の削減: 面倒なボイラープレート記述から解放され、本質的なロジックの実装に集中できます。
  • 堅牢性の向上: デフォルトでイミュータブル(不変)かつ、値による等価性比較(Value Equality)が保証されるため、予期せぬバグを防げます。
  • 安全なデータ操作: copyWith による安全なコピー生成や、JSON変換の自動化により、開発効率が劇的に向上します。

最初は build_runner を実行する手間を少し感じるかもしれませんが、一度この快適さを知ると、手動でデータクラスを書くスタイルには戻れなくなるはずです。特にAPI通信を伴う規模のアプリでは、必須級のツールと言っても過言ではありません。

Discussion