【Flutter】Riverpodで使うProviderの種類をわかりやすくまとめてみた

:::note warn
この記事は昨日Riverpodを触った人がノート感覚でまとめました。
~~本当に吸収が早い学生だと思っているので、求人オファーお待ちしてます。~~
:::
# Provider
> https://riverpod.dev/ja/docs/providers/provider/
> Provider はプロバイダの中で最もベーシックなプロバイダであり、値を同期的に生成してくれます。
```dart:例
/// countProviderの値を２倍にする。
final doubleCountProvider = Provider<int>((ref) {
  final count = ref.watch(countProvider);
  return count * 2;
});

//値を読み込む
int doubleCount = ref.watch(doubleCountProvider);
```
この例では、`countProvider`というプロバイダーを監視し、２倍の値を返すものです。
値を代入することはできません。つまり値を監視して、適当な処理をした値を返す用途として使えます。
他にも、キャッシュとして利用することもできます。
[公式のキャッシュとしてProviderを利用する例](https://riverpod.dev/ja/docs/providers/provider#provider-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%97%E3%83%AD%E3%83%90%E3%82%A4%E3%83%80%E3%82%84%E3%82%A6%E3%82%A3%E3%82%B8%E3%82%A7%E3%83%83%E3%83%88%E3%81%AE%E6%9B%B4%E6%96%B0%E3%81%AE%E6%9D%A1%E4%BB%B6%E3%82%92%E9%99%90%E5%AE%9A%E3%81%99%E3%82%8B)
# StateProvider
> https://riverpod.dev/ja/docs/providers/state_provider
> StateProvider は外部から変更が可能なステート（状態）を公開するプロバイダです。
```dart:例
final countStateProvider = StateProvider<int>((ref) => 0);

//値を読み込む
int countState = ref.watch(countStateProvider);

//書き込む時(パターン1)
ref.read(countStateProvider.notifier).state = 1;

//書き込む時(パターン2)
ref.read(countStateProvider.notifier).update((state) => state + 1);
```
この例では、int型のproviderを定義し、初期値は0となっています。
好きな型をRiverpodで扱えるというものです。後述する`StateNotifierProvider`は関数を定義するなどして、細かいことができますが、こちらは反対で普通の変数を扱うような感覚で利用できます。

書き込みは`update`を使用することで、現在の値を元に新しい値を決めることができます。

# StateNotifierProvider
> https://riverpod.dev/ja/docs/providers/state_notifier_provider
> StateNotifierProvider は StateNotifier（Riverpod が依存する state_notifier パッケージのクラス）を監視し、公開するためのプロバイダです。

後述する`StateNotifier`を扱うためのProviderです。前述の`StateProvider`が変数を管理するものでしたが、こちらはクラスを管理することができます。`StateNotifier`の説明をしてから詳しく説明します
### StateNotifier
`StateNotifierProvider`が扱えるProviderです。クラスとして定義するので、メゾットなどを用いて幅広い使い方ができます。
```dart:例
class CountStateNotifier extends StateNotifier<int> {
  CountStateNotifier() : super(0);

  void increment() {
    state++;
  }
}
```
この例では、int型のStateを作成し、初期値に0を指定しています。
そして、`increment`を呼び出すことにより、値を１増加させます。

### StateNotifierProvider(続き)
```dart:例
final countStateNotifierProvider =
    StateNotifierProvider<CountStateNotifier, int>((ref) {
  return CountStateNotifier();
});

//値を読み込む
int countStateNotifier = ref.watch(countStateNotifierProvider);

//メゾットを呼び出す
ref.read(countStateNotifierProvider.notifier).increment();
```
`StateNotifier`を監視するためのProviderを作成しています。<>の左側には利用したい`StateNotifier`を、右側には返したい型を指定します。

# FutureProvider
> https://riverpod.dev/ja/docs/providers/future_provider
> FutureProvider は非同期操作が可能な Provider であると言えます
```dart:例
/// 5秒後に経過しました！が非同期で返される。
final countFutureProvider = FutureProvider<String>((ref) async {
  await Future.delayed(const Duration(seconds: 5));
  return "5秒経過しました！";
});

// 値を読み込む
AsyncValue<String> countFuture = ref.watch(countFutureProvider);

// Widgetとして利用してみる
countFuture.when(
    loading: () => const Text("Loading"),
    error: (_, __) => const Text("Error"),
    data: (String data) {
      return Text(data);
    })
```
この例はは、lateFutureProviderが5秒後に`5秒経過しました!`となるプログラムです。
非同期なものを使いたい時はこのProviderを使用することになるでしょう。
# StreamProvider
> https://riverpod.dev/ja/docs/providers/stream_provider
> StreamProvider は FutureProvider の Stream 版です。
```dart:例
///1秒ごとにランダムな値を出すStream
Stream<int> randomValueStream() async*{
  final random = Random();
  while(true){
    await Future.delayed(const Duration(seconds: 1));
    yield random.nextInt(100);
  }
}

//Providerを作成
final countStreamProvider = StreamProvider<int>((ref) => randomValueStream());

//値を読み込む
AsyncValue<int> countStream = ref.watch(countStreamProvider);
```
`FutureProvider`のStreamバージョンです。
FirebaseやWebhookなど、リアルタイムで変化する値がある際に使用します。

# ChangeNotifierProvider
> https://riverpod.dev/ja/docs/providers/change_notifier_provider
> ChangeNotifier を Flutter で利用するためのプロバイダです。
:::note alert
公式が非推奨としています。
:::
```dart:例
// 使用するChangeNotifier
class CountChangeNotifier extends ChangeNotifier {
  int count = 0;

  void increment() {
    count ++;
  }
}

//ChangeNotifierProviderを作成
final countChangeNotifierProvider =
    ChangeNotifierProvider<CountChangeNotifier>((ref) {
  return CountChangeNotifier();
});

//読み込む
int countChangeNotifier = ref.watch(countChangeNotifierProvider).count;

//値を変更(1)
ref.read(countChangeNotifierProvider).increment();
//値を変更(2)
ref.read(countChangeNotifierProvider).count++;
```
はい。`StateNotifierProvider`にとても似ていますが、これまでと異なりProvider自体が型を管理していません。
基本的には使わないことをおすすめします。

# まとめ
- Provider[(公式)](https://riverpod.dev/ja/docs/providers/provider)
    - 代入不可（決めた処理に沿った値が出力される）
    - 他のProviderの値を処理してから出力するなどの用途で使える。
    - 一時的なキャッシュとして利用することでパフォーマンスも向上できる。
- StateProvider[(公式)](https://riverpod.dev/ja/docs/providers/state_provider)
    - 代入可能
    - 変数のように利用したい時に便利。シンプル。
- StateNotifierProvider[(公式)](https://riverpod.dev/ja/docs/providers/state_notifier_provider)
    - メゾット経由で更新できる。
    - `StateNotifier`で指定した値を出力する。
    - メゾット等を持たせることができるので、可変なものを管理するのに便利。
- FutureProvider[(公式)](https://riverpod.dev/ja/docs/providers/future_provider)
    - 代入不可
    - 非同期なもので利用すると便利。
    - `watch`すると、`AsyncValue`が返ってくる。
- StreamProvider[(公式)](https://riverpod.dev/ja/docs/providers/stream_provider)
    - 代入不可
    - FutureProviderのStream版
    - FirebaseやWebSocket等で使うととても便利。
    - `watch`すると、`AsyncValue`が返ってくる。
- ~~ChangeNotifierProvider~~(非推奨)[(公式)](https://riverpod.dev/ja/docs/providers/change_notifier_provider)
    - 代入とメゾット経由の更新が両方できる。
    - `watch`すると、`ChangeNotifier`が返ってくる。
    - 可能な限り、`StateNotifierProvider`を使おう。

よろしければLGTMお願いします！！

## サンプルコード
今回利用した6つのProviderを使ったカウンターアプリです。
 - `Provider`は`StateNotifierProvider`を監視して、２倍の値を表示します。
 - `FutureProvider`は5秒後に5秒経過しました！と表示します。
 - `StreamProvider`は1秒ごとに0-100の乱数を表示します。


![IMG_72C035C9DCA9-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362835/e499cc0e-b012-aff7-92fe-da04891e19f8.jpeg)


```dart
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

///Provider
final doubleCountProvider = Provider<int>((ref) {
  final count = ref.watch(countStateNotifierProvider);
  return count * 2;
});

///StateProvider
final countStateProvider = StateProvider<int>((ref) => 0);

///StateNotifierProvider
class CountStateNotifier extends StateNotifier<int> {
  CountStateNotifier() : super(0);

  void increment() => state++;
}

final countStateNotifierProvider =
    StateNotifierProvider<CountStateNotifier, int>((ref) {
  return CountStateNotifier();
});

///FutureProvider
final countFutureProvider = FutureProvider<String>((ref) async {
  await Future.delayed(const Duration(seconds: 5));
  return "5秒経過しました！";
});

///StreamProvider
Stream<int> randomValueStream() async* {
  final random = Random();
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    yield random.nextInt(100);
  }
}

final countStreamProvider = StreamProvider<int>((ref) => randomValueStream());

///ChangeNotifierProvider
class CountChangeNotifier extends ChangeNotifier {
  int count = 0;

  void increment() => count++;
}

final countChangeNotifierProvider =
    ChangeNotifierProvider<CountChangeNotifier>((ref) {
  return CountChangeNotifier();
});

class MyHomePage extends ConsumerWidget {
  final String title;

  const MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    //Provider
    int doubleCount = ref.watch(doubleCountProvider);

    //StateProvider
    int countState = ref.watch(countStateProvider);

    //StateNotifierProvider,StateNotifier
    int countStateNotifier = ref.watch(countStateNotifierProvider);

    //FutureProvider
    AsyncValue<String> countFuture = ref.watch(countFutureProvider);

    //SteamProvider
    AsyncValue<int> countStream = ref.watch(countStreamProvider);

    //ChangeNotifierProvider
    int countChangeNotifier = ref.watch(countChangeNotifierProvider).count;

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Table(
          columnWidths: const <int, TableColumnWidth>{
            0: FlexColumnWidth(0.5),
            1: FlexColumnWidth(0.5),
          },
          children: [
            TableRow(children: [
              const Text("Provider"),
              Text("$doubleCount"),
            ]),
            TableRow(children: [
              const Text("StateProvider"),
              Text("$countState"),
            ]),
            TableRow(children: [
              const Text("StateNotifierProvider"),
              Text("$countStateNotifier"),
            ]),
            TableRow(children: [
              const Text("FutureProvider"),
              Text(countFuture.when(
                  data: (String data) => data,
                  error: (_, __) => "error",
                  loading: () => "loading")),
            ]),
            TableRow(children: [
              const Text("StreamProvider"),
              Text(countStream.when(
                  data: (int data) => "$data",
                  error: (_, __) => "error",
                  loading: () => "loading")),
            ]),
            TableRow(children: [
              const Text("ChangeNotifierProvider"),
              Text("$countChangeNotifier"),
            ]),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(countStateNotifierProvider.notifier).increment();
          ref.read(countStateProvider.notifier).update((state) => state + 1);
          ref.read(countChangeNotifierProvider).count++;
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

```