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

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 だからです。

https://docs-v2.riverpod.dev/docs/about_code_generation

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

https://deploy-preview-2740--river-pod.netlify.app/docs/introduction/why_riverpod

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

## 関数とクラス

code generation の具体的な使い方は割愛しますが、大きくわけて 2 つの種類があるとドキュメントでは整理されています。それが __Functional__ と __Class-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 の例を __Functional__ と __Class-Based__ で書いてみます。

```dart:functional.dart
@riverpod
int multiCount(MultiCountRef ref) {
  final count = ref.watch(countProvider);
  return count * 2;
}
```

```dart: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 な処理を必要とする `FutureProvider` と `AsyncNotifierProvider`、そして値の生成に Stream 処理を必要とする `StreamProvider` と `StreamNotifierProvider` です。

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

# その他の Provider

以上です。

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

## StateNotifierProvider

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

> Prefer using NotifierProvider instead.

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

```dart
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 の __Functional__ と __Class-Based__ のどちらの整理にも含まれない中途半端なものであるためと思われます。

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

# まとめ

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

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

# 余談

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

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