【Flutter】Freezedに独自メソッドを追加する方法
はじめに
Flutter開発において、イミュータブルなデータクラスを生成するために必須級のパッケージである Freezed。
通常はプロパティを定義して終わりですが、「firstName と lastName を結合した fullName を取得したい」や、「特定の条件を満たしているか判定するメソッドを持たせたい」と思ったことはありませんか?
しかし、単にメソッドを書くだけではエラーになってしまいます。
今回は、Freezedのモデルに独自のメソッドやGetterを追加するための「たった1行の重要な記述」について解説します。
参考
結論:プライベートコンストラクタを追加するだけ
Freezedで生成したクラスにメソッドを追加するには、空のプライベートコンストラクタ const ClassName._(); を定義する必要があります。
これがないと、Freezedの生成コードと競合してしまい、正しく動作しません。
コード例
以下は、User クラスに「成人かどうか」を判定する isAdult というGetterを追加する例です。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
abstract class User with _$User {
// ① ここが重要!空のプライベートコンストラクタ
const User._();
// 通常のファクトリコンストラクタ
const factory User({
required String name,
required int age,
}) = _User;
// ② 追加したい独自メソッドやGetter
bool get isAdult => age >= 20;
void sayHello() {
print('Hello, my name is $name');
}
}
なぜこれが必要なのか?
Freezedは通常、データ保持のための入れ物として機能しますが、メソッドを追加したい場合は「このクラスはただのデータ定義ではなく、振る舞いを持つクラス(Base class)である」とDartに認識させる必要があります。
const User._(); という記述は、このクラスをインスタンス化可能なベースとして確立させるための「おまじない」のようなものだと覚えておくと良いでしょう。
公式ドキュメントより
"To enable method definitions, you simply need to define a private empty constructor."
(メソッド定義を有効にするには、プライベートな空のコンストラクタを定義するだけです。)
Riverpodとの相性が抜群に良くなる
このテクニックを知っておくと、Riverpodによる状態管理がよりスッキリ書けるようになります。
通常、ロジックは Notifier (ViewModel的な場所) に書きがちですが、データそのものに関わるロジックは Model(Freezedクラス) に持たせたほうが、コードの見通しが良くなるケースがあります(ドメインロジックの凝集)。
例:状態更新メソッドをModelに持たせる
例えば、カウンターアプリで数値を増やすロジックを考えてみます。
Before:Notifierにロジックが散らばる
// Notifier側で、copyWithを使って計算している
state = state.copyWith(count: state.count + 1);
After:Modelにロジックを集約
まず、Freezedモデル側に更新用メソッドを作ります。
abstract class CounterState with _$CounterState {
const CounterState._(); // おまじない
const factory CounterState({
(0) int count,
}) = _CounterState;
// 自身のコピーを返すメソッドを定義
CounterState increment() {
return copyWith(count: count + 1);
}
}
すると、RiverpodのNotifier側はこうなります。
class Counter extends _$Counter {
CounterState build() => const CounterState();
void increment() {
// 非常に直感的!
state = state.increment();
}
}
このように、「どうデータを変更するか」というロジックをModel側に閉じ込めることができ、Riverpod側は「どのメソッドを呼ぶか」だけに集中できます。これを徹底すると、テストも書きやすくなり、変更に強いコードになります。
まとめ
- Freezedモデルにメソッドを追加したいときは、
const ClassName._();を追加する。 - これだけでGetterや複雑なロジックをモデル内に記述できる。
- Riverpodと組み合わせる際、状態更新ロジックをModelに持たせることで、コードの責務が明確になる。
たった1行の追加ですが、開発の幅がぐっと広がるテクニックです。
Discussion