🚫

Flutterにおけるimmutable(イミュータブル)な状態管理

2024/10/08に公開

はじめに

自分自身はWebエンジニア出身でFlutterでの開発経験はほぼ皆無なのですが、個人的に最近Flutterの勉強を始めています。

今回はFlutterの設計原則で抑えておくとよいとされているimmutable(イミュータブル)についてなぜ必要なのか、どう実装すると良いのかについて調べてみました。

immutableとmutable

不変ということ。immutableなクラスから作成したオブジェクトの値は変更できません。
逆にmutableとは値が変更できるオブジェクトのことを指します。

以前の記事で触れたとおり、Flutterの状態管理はRiverpodを使うのが無難ですが、
Riverpodで状態を監視する方法としてはStateNotifierProviderの使用が推奨されています。

このStateNotifierProviderは状態値をimmutableで扱うことが実質必須となっており、immutableなオブジェクトの扱いを覚えておくことはFlutter開発において重要になります。

なぜimmutableな状態管理を行うのか

Riverpodで例を上げると、StateNotifierProviderのステート(状態)はimmutableである必要があるため、state.add(todo)のような既存オブジェクトに対しての直接の変更はできません。

以下のように既存値と新しい値で新しいリストを作るような書き方をする必要があります。

state = [...state, todo];

このような書き方はReduxではよくある書き方で馴染みのあるWebエンジニアの方も多いのではないでしょうか。Reduxもimmutableなデータ管理を推奨していましたが、ステートを変更するためのロジックを担うモジュールをアーキテクチャで決めておけば、他では値が変わらないことが保証されているため、変更を行っている箇所を後から特定しやすく、コードの保守性が向上します。

デメリットとしては値の変更を行うためにオブジェクトのコピーを行って新しいオブジェクトを作る必要があったり、書き方に慣れるまではとっつきにくい所でしょうか。

freezedパッケージとは?

Flutterではfreezedパッケージを使うとimmutableなクラスを簡単に作成できます。

immutableなクラスを作るにはFlutterデフォルトでは@immutableがありますが、ざっくりな比較表はこちらです。イミュータブルなクラスを作るには決まりきった定型処理を実装する必要があるのですが、それらのボイラープレートコードをfreezedは自動生成してくれるので便利です。

特徴 freezed @immutable
ボイラープレートの削減 ✅ 大幅に削減 ❌ 多くのボイラープレートコードが必要
イコール演算子のオーバーライド ✅ 自動生成 ❌ 手動実装が必要
hashCode の実装 ✅ 自動生成 ❌ 手動実装が必要
toString メソッドの実装 ✅ 自動生成 ❌ 手動実装が必要
コピー機能 (copyWith) ✅ 自動生成 ❌ 手動実装が必要

手順としては、

  • クラスに@freezedというアノテーションをつける
  • flutter pub run build_runner buildコマンドを実行する

そうすると、user_state.freezed.dartのようなボイラーコードありの別ファイルが生成され、ボイラーコードを元ファイルに実装する必要がなくなります。

下記のような変更処理のメソッドで@freezedが用意してくれるcopyWithを使えるので便利です。


class UserState with _$UserState {
  const factory UserState({
    required String name,
    required int age,
    (false) bool isLoggedIn,
  }) = _UserState;

  const UserState._();

  UserState login() => copyWith(isLoggedIn: true);

  UserState updateName(String newName) => copyWith(name: newName);

  UserState incrementAge() => copyWith(age: age + 1);
}

ただimmutableなクラスを作るたびにファイル生成が必要なのが面倒なので、
以下の記事のようにエディタに自動生成のプラグインをいれるなどの工夫は必要そうです。
https://zenn.dev/satoru_inoue/articles/18c58f7c786269

さいごに

今回紹介したfreezedは以前の記事でいうと、State(ViewModel)を実装する時に使えると良さそうです。

Riverpodの具体的な使い方は別途キャッチアップする必要はあるものの、Flutterにおけるイミュータブルな状態の実装のイメージはついたんじゃないかなと思います。

Discussion