LoginSignup
28
12

【Flutter】Riverpod の Provider たちを一度整理しようと思う(走り書き)

Last updated at Posted at 2023-08-08

Riverpod にはいろいろな種類の Provider が用意されています。

2023.8.8 時点(バージョンは 2.3.6)では、以下の Provider がドキュメントの All Providers のメニューに列挙されています。

  • Provider
  • (Async)NotifierProvider
  • StateNotifierProvider
  • FutureProvider
  • StreamProvider
  • ChangeNotifierProvider

ちょっとひとつずつドキュメントを開いて違いを比較するには多く、また気づいたら非推奨になっているものもありますので、この記事でいったん現時点での Provider の使い分けをまとめてみたいと思います。

Code generation

Riverpod の Provider の使い分けを考える上で、 code generation の話題は避けて通れません。

Provider の種類が多く初学者にとって使い分けが難しい問題は作者の Remi さんも認識しており、それを「Provider の種類を考えずとも最適なものを生成する」というアプローチで解決しようとしているのが code generation だからです。

その考えを反映してか、現在 Remi さんが整備中の新しいドキュメントでは、 All Providers の項目が左メニューの 下から2番目 まで後回しにされています。

Provider の種類は、 ProviderContainerProviderElement などのように「内部的に使われているクラス」という扱いにそのうちなるのかもしれません。

関数とクラス

code generation の具体的な使い方は割愛しますが、大きくわけて 2 つの種類があるとドキュメントでは整理されています。それが FunctionalClass-Based です。

Providers can be defined either as an annotated function or as an annotated class

これは、State を生成・更新するロジックが「関数ひとつで完結する」ものなのか、「クラス(から生成されたオブジェクト)を必要する」ものなのか、という違いです。

たとえば他の Provider の値を .watch() し、そこに何かしらのロジックを施して得られる値でよければ Functional が適切ですし、値の更新にメソッドや private なフィールドなどを必要とする場合は Class-Based となります。

たとえば、カウントを管理する countProvider.watch して何か値を保持する Provider の例を FunctionalClass-Based で書いてみます。

functional.dart
@riverpod
int multiCount(MultiCountRef ref) {
  final count = ref.watch(countProvider);
  return count * 2;
}
class_based.dart
@riverpod
class MultiCount with _$MultiCount {
  // private なフィールド
  var _multiplier = 2;
  
  @override 
  int build() {
    final count = ref.watch(countProvider);
    return count * _multiplier;
  }

  // 外部に提供するメソッド
  void multiplyWith(int value) {
    _multiplier = value;
    state = state * _multiplier;
  }
}

code generation において、 Functional な方法で作られる Provider が Provider とその派生であり、 Class-Based に生成される Provider は NotifierProvider とその派生となります。

「その派生」というのは、値の生成に Future な処理を必要とする FutureProviderAsyncNotifierProvider、そして値の生成に Stream 処理を必要とする StreamProviderStreamNotifierProvider です。

詳しい分類表は公式ドキュメントを参照してください。

その他の Provider

以上です。

まだたくさんあるじゃないかと思われるかもしれませんが、積極的な利用を推奨されているものは以上の code generation がサポートする 6 種類の Provider のみです。その他はすべて「非推奨」となっています。理由としては、「パッケージ利用者に混乱を与えるから」ということのようです。

StateNotifierProvider

ドキュメントを見ると、以下の注釈が書かれています。

Prefer using NotifierProvider instead.

細かな理由は書かれていませんが、おそらく用途が NotifierProvider と似ていること、また利用する際

final someProvider = StateNotifierProvider<SomeNotifier, SomeState>((ref) {
  // ① 
  return SomeNotifier();
});

class SomeNotifier extends StateNotifier<SomeState> {
  SomeNotifier() : super(SomeState());

  // ②
}

のような形でコーディングすることになり、

  • ref.watch などの処理を ① と ② のどこにどう書けば良いのか
  • ref はどう扱えば良いのか、 SomeNotifier に渡して良いのか

など考えさせられる部分が多く、「すべてをクラス内に書けば良い」 NotifierProvider の方が混乱が少ないためと推測しています。

ChangeNotifierProvider

こちらも同様に NotifierProvider が推奨されています。

Prefer using NotifierProvider instead.
Consider using ChangeNotifierProvider only if you are absolutely certain that you want mutable state.

もし State をどうしても mutable(可変)にしたい場合に、 notifiyListeners を呼び出し忘れないことをコーディング側が担保しながら利用する形になります。

StateProvider

こちらはドキュメントに記載はないものの、 Riverpod の Discord サーバーなどで頻繁に非推奨である旨が発言されています。

おそらく code generation の FunctionalClass-Based のどちらの整理にも含まれない中途半端なものであるためと思われます。

基本的に (Provider ではなく) StateProvider を使いたい場合というのは、state を外部から変更したい場合になると思いますので、この場合は NotifierProvider (とその派生)を使う形になるかと思います。

まとめ

以上、走り書きで Riverpod の Provider を整理してみました。

ドキュメント上では整理しきれていませんが、今後 Dart に対する static metaprogramming の導入によって code generation による記法のみがサポートされるようになり、従来の書き方は廃止されることも発言されていますので、なるべく早い段階で code generation が対応している 6 つの Provider への乗り換えと、できる限り code generation を使った記法へ移行していく必要がありそうです。

余談

公式ドキュメントの日本語訳ページは、 NotifierProvider に関する情報や、StateNotifierProvider などに対する NotifierProvider を推奨する文言などの追記が追いついていない状況です。(翻訳はどうしても有志の方の協力が必要なため、仕方ない部分と思います)

日本語訳があるからと日本語訳ページのみを読んでしまうとクリティカルな部分で「古い情報」が残ってしまっているリスクがありますので、必ず原文である英語を一読することをお勧めします。

28
12
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
12