flutter freezedを使って安全かつ分かりやすいデータ構造を実現する方法

モバイル開発

はじめに

Flutterアプリ開発では、データ構造を明確に保つことが重要ではないでしょうか。 データクラスを作る場合には、変更がどこで起きたのかを追跡しやすく、かつプログラムの動きを見通しやすい設計が求められます。 そのような場面で flutter freezed が役立ちます。 Dart言語のコード生成機能を活用し、イミュータブルなデータクラスを簡単に作成できるので、初心者の皆さんにも検討する価値があるでしょう。

しかし「何となく使うと便利そう」と考えているだけでは、具体的な活用イメージが湧きにくいかもしれません。 この記事では、flutter freezedとは何か、どうやって導入するか、実務レベルでのメリットは何か、といった点を初心者でも理解しやすいように解説していきます。

flutter freezedとは何か

flutter freezed はDartでイミュータブルなデータクラスやシーリングクラスを自動生成するためのライブラリです。 Dartのアノテーションやビルドランナー機能と組み合わせることで、コンストラクタやメソッドをひとつひとつ書かなくても済むようになります。 とくにアプリ開発では「データに対する更新処理を抑制しつつ、変更があった場合には分かりやすくコード上で扱いたい」と考える場面が多いですよね。

freezedを使えば、Dartのクラス定義で @freezed というアノテーションを付け、あとは build_runner を実行するだけで、copyWithtoJson などのメソッドを自動生成できます。 Flutterでの状態管理を行う際にデータを破壊的に更新しない習慣を身につけられるので、コードの安全性が高まりやすいでしょう。 また、イミュータブルであるという前提がテストやデバッグにも有益に働くため、チーム開発でも採用例が増えています。

freezedがもたらすメリット

freezedを導入すると、コードの保守性や可読性の向上が期待できます。 以下のポイントは、実際の現場で多くの開発者がメリットと感じている部分でしょう。

1つ目は、イミュータブルに管理できる点です。 データオブジェクトを自由に変更してしまうと、予期しないバグや意図しない挙動が発生しがちです。 freezedは不変のデータクラスを簡単に作れるため、状態管理でも混乱が減るでしょう。

2つ目は、copyWith などの便利メソッドを自動生成してくれる点です。 わざわざ同じような処理を書く必要がなくなるので、開発効率を高めながらコードの重複を防げます。

3つ目は、シーリングクラスによるパターンマッチングの強化です。 クラスの種類が増えてくると、「どのパターンにもきちんと対応できているか」が分かりにくくなります。 freezedでは whenmaybeWhen といったメソッドを使って、想定外のケースがあれば簡単に検知できます。

導入方法

freezedを利用するには、pubspec.yamlに関連パッケージを追加し、その後ビルドランナーを実行する手順が基本です。 DartやFlutterのバージョンは新しい方が好ましいので、プロジェクトを最新の状態にしておくと安心ではないでしょうか。

# pubspec.yaml の dependencies と dev_dependencies に以下を追加
dependencies:
  freezed_annotation: ^2.3.2

dev_dependencies:
  build_runner: ^2.4.6
  freezed: ^2.3.2

上記を追記したら、以下のコマンドをプロジェクトルートで実行します。

flutter pub get
flutter pub run build_runner build

これで freezed_annotation を読み込み、アノテーションをもとにコード生成を行う準備が整います。 プロジェクトによってはバージョンの衝突に注意が必要な場合がありますが、バージョンを合わせることでスムーズに導入できるはずです。

依存関係をアップデートするときは、ほかのパッケージとバージョンが競合しないかにも気を配る必要があります。

コンストラクタとイミュータブルオブジェクト

freezedを使うときは、Dartファイルに @freezed を付与してクラスを定義し、factory コンストラクタを記述します。 このとき、part ファイル(.g.dartや .freezed.dart)も生成されるため、ファイル上部に part 'xxx.freezed.dart'; のような宣言を忘れずに追加しましょう。

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';


class User with _$User {
  factory User({
    required String id,
    required String name,
    int? age,
  }) = _User;
}

このように書くと、User クラスは不変(イミュータブル)な構造になります。 値を更新したい場合は copyWith を利用し、元のオブジェクトを保持したまま新しいオブジェクトを生成する形になるため、データの追跡がしやすいのが特徴ですね。

シーリングクラスの利用方法

freezedの魅力のひとつが、シーリングクラスを通じて複数のクラスパターンを一括管理できる点です。 例えば、アプリ内で取得するデータに状態を持たせたい場合、SuccessやErrorなどをまとめて一つのクラスの下に表現できるのです。

import 'package:freezed_annotation/freezed_annotation.dart';

part 'fetch_result.freezed.dart';


class FetchResult with _$FetchResult {
  const factory FetchResult.success(String data) = Success;
  const factory FetchResult.error(String message) = ErrorDetails;
}

上記の例では、FetchResult がシーリングクラスとして機能します。 実際にコードを書くときには、when メソッドで確実に全パターンを網羅できるようになり、取りこぼしを防ぐのに役立ちます。

final result = await fetchDataFromApi();

result.when(
  success: (data) {
    print("成功: $data");
  },
  error: (message) {
    print("エラー: $message");
  },
);

こうした明確な構造は、後からコードを読む際の理解を助けるでしょう。

JSONシリアライズとの連携

Flutterアプリ開発では、Web APIからJSONデータを取得し、それをオブジェクトに変換する処理がよくあります。 freezedは toJsonfromJson を自動で作ってくれるので、この手の変換処理をスムーズに書けます。

JSONシリアライズを使うには、freezed_annotation に加えて json_serializable なども組み合わせることになります。 クラス定義には @JsonSerializable() のアノテーションを加えたうえで、part 'xxx.g.dart'; も同時に指定します。 すると、以下のように User.fromJsonuser.toJson() を簡単に扱えるようになります。

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';


class User with _$User {
  ()
  factory User({
    required String id,
    required String name,
    int? age,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

API通信でレスポンスを受け取ったら User.fromJson を呼び出し、取得した情報をそのままオブジェクトに変換できるため、処理が読みやすくなるのではないでしょうか。

状態管理との組み合わせ

Flutterでアプリを作る場合、ProviderやRiverpod、Blocなど、さまざまな状態管理の手法があります。 freezedはイミュータブルなクラスを生成するため、こうした状態管理パッケージと相性が良いとされています。 アプリの状態を一貫して扱うためには、データが意図せず書き換えられない仕組みが必要です。

たとえば、Blocパターンでイベントと状態を明確に区別するときにも、freezedでシーリングクラスを定義しておくと「現時点で考慮すべき状態はどれなのか」が可視化しやすくなるでしょう。 また、copyWith を使って特定のフィールドだけ更新する実装も容易に行えるので、細かい画面更新や内部状態の変化を安全に扱えます。

状態管理の部分こそイミュータブル設計の恩恵が大きいところです。 将来的に複雑な機能を追加するときも、整合性が保ちやすいです。

まとめ

flutter freezedは、データ構造の安全性と可読性を高めるための選択肢になりそうですね。 イミュータブルなオブジェクトをベースに、シーリングクラスやJSONシリアライズ、copyWithなど多彩な機能を自動生成してくれます。 実務レベルでも導入しやすい仕組みのため、初心者の皆さんにとっても理解が進めば大きな武器になるはずです。

実際には、アプリの機能が拡大してデータパターンが増えたり、状態管理が複雑になったりすると、管理の仕方に頭を悩ませることがあるかもしれません。 そんなときこそ、イミュータブルなクラスとパターンマッチングを活用することで、想定外のケースを早期に発見したり、修正箇所を特定したりしやすくなるでしょう。

flutter freezedの導入でコードが分かりやすく保てれば、より安定したFlutter開発を楽しめるのではないでしょうか。

Flutterをマスターしよう

この記事で学んだFlutterの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。