【Flutter】Riverpodクラスの使い分け(Riverpod2.0以降)
さて、久々にととのっていきましょう🧖
実務で長らくStateNotifierを使ってきましたが、Riverpod2.0でNotifierとAsyncNotifierが登場しました。
これにより、おそらくStateNotifierはレガシーになったようです。(え〜)
さらに、Riverpod2.3以降はStreamNotifierも追加されています。(え〜)
新規プロジェクトでの開発などに向け、Riverpodについてもう一度整理しようと思ってのこの記事です。
Riverpodクラスの使い分け
はじめにState管理クラスについて大雑把に整理しておくと、それぞれの主なユースケースとRiverpod2.0以降の評価は以下のようになります。
No | クラス | ユースケース | 評価 |
---|---|---|---|
1 | Provider | ステートが変わらないRipositoryクラスなどのインスタンス取得に使う(参考) | 使える |
2 | StateProvider | プリミティブな変数(String、enumなど)などの取得、更新に使う(参考) | 使えるけどレガシー |
3 | StateNotifier | ステート(immutable)が変わるViewModelクラスなどのインスタンス取得、更新に使う(参考) | 使えるけどレガシー |
4 | FutureProvider | API通信などの非同期処理の結果取得に使う(参考) | 使える |
5 | StreamProvider | リアルタイムAPI通信などの非同期処理の結果取得に使う(参考) | 使える |
6 | ChangeNotifier | ステート(mutable)が変わるViewModelクラスなどのインスタンス取得、更新に使う | 基本使わない(非推奨) |
7 | Notifier (Riverpod2.0で登場!) |
No.3のユースケースとほぼ同じ(参考) | 使える |
8 | AsyncNotifier (Riverpod2.0で登場!) |
No.4のユースケースに加えて、ステート更新などのメソッドも加えたい場合に使う(参考) | 使える |
9 | StreamNotifier (Riverpod2.3で登場!) |
No.5のユースケースに加えて、ステート更新などのメソッドも加えたい場合に使う(参考) | 使える |
このうち今回は、使いどころに迷いそうな、「使えるけどレガシー」になったStateProviderとStateNotifierについて踏み込んでいきます。
「使えるけどレガシー」なクラスの置き換え
結論から言うと、
- StateProvider => Notifier
- StateNotifier => Notifierまたは(非同期処理を含むなら)AsyncNotifier
に置き換えるのがクールですね、ということなのですが、そうなる流れを具体的なケースでご紹介します。
StateProvider => Notifierの置き換えについて
数をカウントするクラスをStateProviderで実装します。
final countProvider = StateProvider<int>((ref) {
return 0;
});
これをNotifierを使って、
final countNotifierProvider = NotifierProvider<CountNotifier, int>(CountNotifier.new);
class CountNotifier extends Notifier<int> {
int build() {
return 0;
}
}
のように置き換えられます。
あれ、StateProviderより何だか冗長になっている!?
はい、そうなんです。StateProviderより冗長になってます。(え〜)
ここではまだStateProviderをNotifierに置き換える良さがわかりませんが、このあとのStateNotifier => Notifierの置き換えも踏まえることでその良さがわかるかと思います。
StateNotifier => Notifierの置き換えについて
より柔軟にステートを更新するためにStateProviderでは物足りないことがあります。
そこで、これまではStateNotifierを使ってきました。
数をカウントするクラスをStateNotifierで実装すると以下のようになります。
ステートを更新するincrementメソッドを追加しています。
final countNotifierProvider = StateNotifierProvider<CountNotifier, int>((ref) {
return CountNotifier();
});
class CountNotifier extends StateNotifier<int> {
CountNotifier() : super(0);
void increment() {
state++;
}
}
これをNotifierを使って、
final countNotifierProvider = NotifierProvider<CountNotifier, int>(CountNotifier.new);
class CountNotifier extends Notifier<int> {
int build() {
return 0;
}
void increment() {
state++;
}
}
のように置き換えられます。
StateProviderもStateNotifierもシンプルにNotiferに一本化できることがわかりました。
とはいえ、これまで通りStateNotifierに一本化することもできます。
どうしてStateNotifierではなくNotifierが良いのでしょう?
今後はおそらくNotifier(やAsyncNotifier)が優先的にメンテナンスされていくため、というのが大きな理由ですが、その他にも以下のようなメリットが挙げられます。
- refがNotifier(やAsyncNotifier)の親クラスで実装されているため、refの受け渡しが必要ない
- Notifier(やAsyncNotifier)のbuildメソッドが優れており、コンストラクタが必要ない
- buildメソッド内でRef.watchまたはRef.listenで外部のステートを監視している場合、そのステートに変更があればbuildメソッドが再実行される
- buildメソッドが再実行される間、Notifer(例でいうCountNotifier)のインスタンスが作り直されるわけではなく、元のインスタンスの状態が保たれる
- Notifier(やAsyncNotifier)のProviderの定義が簡単になる上、Riverpod Generatorを使うことでProviderの自動生成もできる(詳細は割愛)
StateNotifier => AsyncNotifierの置き換えについて
最後にAsyncNotifierについて見ていきましょう。
StateNotifierで非同期処理を扱う場合、以下のようにこれまではAsyncValueを使っていましたが、冗長さがありました。
final countAsyncNotifierProvider = StateNotifierProvider<CountAsyncNotifier, AsyncValue<int>>((ref) {
return CountAsyncNotifier(ref.read);
});
class CountAsyncNotifier extends StateNotifier<AsyncValue<int>> {
CountAsyncNotifier(this._reader) : super(const AsyncLoading()) {
// API通信などの非同期処理で初期化
}
final Reader _reader;
Future<void> increment() async {
final countRepository = _reader(countRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(countRepository.increment);
}
}
これをNotifierを使って、
final countAsyncNotifierProvider = AsyncNotifierProvider<CountAsyncNotifier, int>(CountAsyncNotifier.new);
class CountAsyncNotifier extends AsyncNotifier<int> {
FutureOr<int> build() {
// API通信などの非同期処理で初期化
}
Future<void> increment() async {
final countRepository = ref.read(countRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(countRepository.increment);
}
}
のように置き換えられます。
refの受け渡しやコンストラクタがなくなり、スッキリしましたね。
まとめ
Riverpod2.0以降は以下のように置き換えるとととのうでしょう🧖
- StateProvider => Notifier
- StateNotifier => Notifierまたは(非同期処理を含むなら)AsyncNotifier
Discussion