Flutterアプリ開発において、データクラスの作成で冗長なコードを書いていませんか?equals、hashCode、toString、copyWithメソッドを手動で実装するのは面倒で、バグの温床にもなりがちです。そんな問題を解決してくれるのがFreezedです。
今回は、実際のクイズアプリプロジェクトを例に、Freezedを使った型安全なモデル設計の実践方法を紹介します。
参考プロジェクト:https://github.com/morisakisan/share_quiz
Freezedとは何か?
Freezedは、Dartでimmutable(不変)なデータクラスを簡単に作成できるコード生成ライブラリです。アノテーションを使って宣言するだけで、equals、hashCode、toString、copyWithなどのメソッドを自動生成してくれます。また、JSON serialization/deserializationにも対応しており、APIとの連携も簡単に行えます。
使い所
Freezedは主にデータモデルクラスの作成に使用します。特にFirestoreなどのデータベースとの連携、API通信でのデータ転送、状態管理でのデータ保持など、アプリケーション全体でデータを安全に扱いたい場面で威力を発揮します。immutableなオブジェクトを簡単に作成できるため、予期しないデータ変更を防ぎ、バグの少ない堅牢なアプリケーションを構築できます。
ライブラリの読み込み
pubspec.yamlに以下を追加します:
dependencies:
freezed_annotation: 3.1.0
dev_dependencies:
freezed: 3.2.3
build_runner: 2.8.0
json_serializable: 6.11.1 # JSON対応が必要な場合
実際のコード例
ここからは、実際のクイズアプリプロジェクトで使用しているFreezedのコードを見ていきましょう。Clean Architectureを採用したプロジェクトで、domain層とdata層でFreezedを活用している例です。
ドメインモデルでの基本的な使用
アプリのコアとなるQuizモデルの例です。
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'quiz.freezed.dart';
@freezed
abstract class Quiz with _$Quiz {
const factory Quiz({
required String documentId,
required String title,
required String question,
required List<String> choices,
required int correctAnswer,
required DateTime? createdAt,
required double? correctAnswerRate,
required int? answerCount,
required int? goodCount,
required List<String> imageUrls,
}) = _Quiz;
}
ポイント:
@freezed
アノテーションで自動生成を指定const factory
でimmutableなコンストラクタを定義required
で必須パラメータを明示- nullable型(
?
)で任意パラメータを表現
JSON対応のDTOクラス
FirestoreとのデータやりとりでJSON serialization/deserializationが必要なDTOクラスの例です。
import 'package:flutter/foundation.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:share_quiz/data/json_converter/timestamp_converter.dart';
part 'quiz_dto.freezed.dart';
part 'quiz_dto.g.dart';
@freezed
abstract class QuizDto with _$QuizDto {
const factory QuizDto({
@JsonKey(includeFromJson: true, includeToJson: true) String? docId,
@JsonKey(name: 'correct_answer') required int correctAnswer,
@JsonKey(name: 'title') required String title,
@JsonKey(name: 'question') required String question,
@JsonKey(name: 'image_url') required List<String> imageUrl,
@JsonKey(name: 'choices') required List<String> choices,
@TimestampConverter()
@JsonKey(name: 'created_at') required DateTime? createdAt,
@JsonKey(name: 'uid') required String uid,
@JsonKey(name: 'correct_answer_rate') required double? correctAnswerRate,
@JsonKey(name: 'answer_count') required int? answerCount,
@JsonKey(name: 'good_count') required int? goodCount,
}) = _QuizDto;
factory QuizDto.fromJson(Map<String, dynamic> json) =>
_$QuizDtoFromJson(json);
}
ポイント:
part 'quiz_dto.g.dart';
でJSON serialization用ファイルを指定@JsonKey(name: 'field_name')
でJSONフィールド名をマッピング@TimestampConverter()
でカスタムコンバーターを使用factory fromJson
でJSON deserializationメソッドを定義
カスタムコンバーターの実装
FirestoreのTimestamp型をDateTime型に変換するカスタムコンバーターの例です。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
class TimestampConverter implements JsonConverter<DateTime?, Object> {
const TimestampConverter();
@override
DateTime? fromJson(Object timestamp) {
if (timestamp is Timestamp) {
return timestamp.toDate();
}
return null;
}
@override
Object toJson(DateTime? date) {
if (date == null) {
return FieldValue.serverTimestamp();
}
return Timestamp.fromDate(date);
}
}
ポイント:
JsonConverter
インターフェースを実装fromJson
でJSONからDartオブジェクトに変換toJson
でDartオブジェクトからJSONに変換- Firestore特有のTimestamp型を適切に処理
フォーム用のモデル
ユーザー入力を扱うフォーム用のモデルの例です。
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'quiz_form.freezed.dart';
@freezed
abstract class QuizForm with _$QuizForm {
const factory QuizForm({
String? title,
String? question,
File? image,
List<String>? choices,
int? answer,
}) = _QuizForm;
}
ポイント:
- すべてのフィールドをnullableにしてフォームの段階的入力に対応
File
型で画像ファイルを扱う- シンプルな構造でフォームデータを管理
コード生成の実行
Freezedのコードを書いた後は、以下のコマンドでコード生成を実行します:
# 一回だけ実行
flutter packages pub run build_runner build
# ファイル変更を監視して自動実行
flutter packages pub run build_runner watch
Freezedの利点
型安全性:
コンパイル時に型チェックが行われ、実行時エラーを防げます。
Immutability:
オブジェクトが不変なため、予期しないデータ変更を防げます。
コード削減:
equals、hashCode、toString、copyWithメソッドが自動生成されます。
JSON対応:
json_serializableと組み合わせて簡単にJSON変換が可能です。
パターンマッチング:
Union typesを使った高度なパターンマッチングも可能です。
まとめ
Freezedを使うことで、型安全で保守しやすいデータモデルを簡単に作成できます。特にFirebaseやAPIとの連携が多いFlutterアプリケーションでは、JSON serialization/deserializationの機能と組み合わせることで、開発効率が大幅に向上します。
今回紹介した基本的な使い方をマスターすることで、バグの少ない堅牢なFlutterアプリケーションを構築できるようになります。Clean Architectureと組み合わせることで、さらに保守性の高いコードベースを実現できます。
コメント