Riverpod (Flutter Roadmap Riverpod)

## はじめに
Flutterを網羅的に学習するにあたって[Roadmap](https://roadmap.sh/flutter)を使って学習を進めることにしました。

この記事では、Flutter初学者やこれからFlutterを学習し始める方に向けて、**Riverpod**についてまとめています。

RoadmapはFlutterだけでなく、他の言語やスキルのロードマップも提供されており、何から学習して良いか分からないと悩んでいる方にとって有用なサイトになっています。
ぜひ[Roadmap](https://roadmap.sh/)を利用して学習してみてください。

## Roadmapとは

簡潔に言えば、Roadmap.shは学習者にとっての**ガイドブック**であり、**学習の方向性を提供する学習ロードマップサイト**です。

初心者から上級者まで、ステップバイステップでスキルを習得するための情報が提供されています。

学習の進め方が分かりやすく示されているだけでなく、個々の項目に参考資料やリソースへのリンクも提供されているので、学習者は目標を設定し、自分自身のペースで学習を進めることができます。

## Riverpod
FlutterロードマップRiverpodでは以下の5つのサイトが紹介されています。興味のある方はぜひお読みください。

- riverpod: https://pub.dev/packages/riverpod
- Riverpod in Flutter: https://docs.flutter.dev/data-and-backend/state-mgmt/options#riverpod
- Documentation: https://riverpod.dev/
- Documentation v2 is in progress: https://docs-v2.riverpod.dev/
- Flutter Riverpod 2.0: The Ultimate Guide: https://codewithandrea.com/articles/flutter-state-management-riverpod/

## Riverpodとは
Riverpodは、Flutterアプリケーションのための状態管理ライブラリで、Provider パッケージのようにウィジェットツリー内でデータを提供するために使用されます。Riverpodは、Provider パッケージを改良し、柔軟性と拡張性を向上させたもので、複雑な状態を簡単に管理することができるライブラリです。

Riverpodを使用することで、任意の Widget にデータを受け渡すことが出来るようになります。

## Riverpodの特徴
- **スコープの明確化:** Riverpodでは、プロバイダーのスコープがより厳密に定義されてい流ため、Providerごとに異なるスコープを持たせることができ、依存関係をより制御しやすくしています。

- **リアクティブなデータ変更:** リアクティブな変更通知を提供しており、データが変更された際に関連するウィジェットが自動的に再構築される仕組みをサポートしています。

- **様々なProvider:** Riverpodでは、 `Provider` だけでなく、 `FutureProvider` 、 `StreamProvider` など、異なるデータソースに対するプロバイダーが統一的なAPIで提供されています。

- **依存関係の設定:** 依存関係を自動的に解決し、プロバイダー間の依存関係を簡単に設定できるようになっています。

- **Null Safety:** `Null Safety` をサポートしており、安全性と安定性が向上しています。

- **利用範囲の広さ:** 小規模なアプリケーションから大規模なアプリケーションまで幅広く利用できます。プロバイダーのスコープの明確さと柔軟性により、アプリケーションのサイズに合わせて効果的に使用できます。

- **リアクティブなUIの構築:** リアクティブな変更通知をサポートしているため、データの変更に応じてUIが自動的に更新されるようなアプリケーションに適しています。

- **非同期データの処理:** `FutureProvider` や `StreamProvider` を使用して非同期データを処理し、リアルタイムなデータの表示や取得が必要な場合に有用です。

- **テスト容易性:** グローバル変数として定義するため、アクセスが保証されるのと同時にテスト容易性も担保されます。複雑な setUp / tearDown ステップが手順が不要になり、Providerを上書きできるので特定の動作テストが簡単です。

- **パフォーマンスの向上:** 「状態」を別の「状態」と組み合わせることも簡単に行え、状態の変化によって影響を受けるもののみが再計算されるのでWidgetの再構築が最適化されます。

## Providerとの違い
Providerパッケージは `InheritedWidget` を改良する形で開発されたパッケージでWidgetツリーに依存しています。親のWidgetツリーを購読して、登録されている `Provider` にアクセスするため、親のWidgetツリーには使用したい `Provider` が登録されている必要があります。

もし `Provider` が登録がされていない場合は `ProviderNotFoundException` エラーが発生してしまいます。

一方でRiverpodパッケージは、 `Provider` をWidgetツリーから切り離してグローバルに定義する事が出来るため、定義した `Provider` に確実にアクセスする事が出来ます

また、Providerパッケージでは同じ型のものが複数同時に使用できないのに対して、Riverpodパッケージでは同じ型の `Provider` を複数参照できるため、Providerパッケージの欠点を補ってくれています。

## Riverpodパッケージ
Riverpodのパッケージには3種類あります。

|パッケージ名|形態|説明| 
|---|---|---|
|**[flutter_riverpod](https://pub.dev/packages/riverpod)**|Flutterのみ|RiverpodをFlutterと統合するためのパッケージです。Flutterアプリケーション内での Riverpod の利用においては、通常このパッケージを使用します。|
|**[hooks_riverpod](https://pub.dev/packages/flutter_riverpod)**|Flutter + flutter_hooks|ReactのHooks APIのような、ウィジェットを構築するためのフック（Hooks）を提供します。再利用可能で理解しやすいコードを書くことができるため、Riverpod をよりシンプルに使用するための手段として使用されます。|
|**[riverpod](https://pub.dev/packages/hooks_riverpod)**|Dart のみ|Flutterに関連するすべてのクラスが削除された、Dartのみのバージョンです。Flutter機能を使用しないパッケージではこちらを使用することになります。|


## 使い方

### 1. ProviderScopeをルートに追加
Riverpodを使った `Provider` の宣言自体はグローバルですが、 `Provider` をアプリ内で利用するために範囲（スコープ）を指定する必要があります。アプリのルート部分を `ProviderScope` でラップすることで、下位ツリーのWidgetで `Provider` を呼び出すことができるようになります。。

```dart: main.dart
void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}
```

### 2. Providerをグローバル変数として定義

プロバイダの作成方法には手動でコードを設定する方法とコード生成を使用する方法の２種類があります。

#### 手動で設定
`Provider` の引数に指定したコールバック関数で `return` するものが管理するデータになります。コールバック関数の引数の `ref` は他のProviderのデータを取得したい時に使用します。Providerのデータを取得していない場合は、 `ref` ではなく `_` として問題ありません。以下は手動設定の場合のサンプルコードです。

```dart: 手動で設定
final myProvider = Provider<MyApp>((ref) {
  return MyApp();
});
// アロー構文を使用した場合
final myProvider = Provider((ref) => MyApp());
```

- `final myProvider` は、定数の宣言です。定数名には使用する `Provider` の名前を定義します。

- `Provider<T>` で Riverpod で使う `Provider` の種類を設定しています。 

- `<MyApp>` は型の宣言を行っています。この `Provider` は `MyApp` という型を返すということを明示的に示しています。

- `((ref) { return MyApp(); });` では、 `ref` という引数を受け取り、 `MyApp()` という値を返しています。`ref` を使用しない場合は `((_) { return MyApp(); });` のようにして省略することも可能です。

- `ref` は `Riverpod` のRefオブジェクトを表します。Refを使うことで他のプロバイダーにアクセスしたり、状態の変更を通知したりできます。以下は `ref` の使用例です。

- **他のプロバイダーを読み込む**
```dart
final user = ref.read(userProvider);
```

- **StateNotifierの変更を通知する**
```dart
ref.read(countProvider.notifier).update((state) => state + 1);
```

- **Riverpodで提供されているメソッドを利用する**
```dart
ref.listen<String>(helloProvider, (prev, next) {
  // 状態変更時の処理 
});
```

#### コード生成

```dart: コード生成
part 'my_provider.g.dart';

@riverpod
MyApp my(MyRef ref) {
  return MyApp();
}
```

- コード生成を使用するので、 `part: '{file_name}.g.dart';` の宣言が必須です。

- `@riverpod` アノテーションをつけて、プロバイダ生成のためのメソッドであることを示します。

- メソッド先頭の`MyApp`は、関数の戻り値の型を指定しています。

- 関数名である `my` はプロバイダの名前になります。設定で変更することもできますが、コード生成を行うと末尾に `Provider` が自動で付くので、 `myProvider` という名前になります。 

- 関数のパラメータである `(MyRef ref)` はコード生成される `MyRef` を関数内で使用するためのものです。コード生成前はコンパイルエラーになりますが、コード生成を実行すれば生成されるものなので問題ありません。

### 3. Providerからデータを取得する
`Provider` からデータを取得するためには `ConsumerWidget` を使用します。 `ConsumerWidget` を継承した Widget を定義すると、 `build()` に `WidgetRef` 型の引数が追加されます。 追加された `WidgetRef` 型の引数に `watch()` または `read()` のメソッドを使用して、 `Provider` を指定することで `Provider` が管理しているデータを取得することができます。

データの変更を監視する必要がある時は `watch()` を、監視する必要がない時は `read()` を使用してデータを取得します。以下のコードはProviderからデータを取得するカウンタアプリのサンプルコードです。

```dart: カウンタアプリサンプルコード
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class CountNotifier extends StateNotifier<int> {
  CountNotifier() : super(0);

  void increment() => state++;

  void decrement() => state--;
}

final countProvider =
    StateNotifierProvider<CountNotifier, int>((ref) => CountNotifier());

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Riverpod Counter',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(countProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Count',
              style: TextStyle(fontSize: 32),
            ),
            Text(
              '$count',
              style: const TextStyle(fontSize: 40),
            ),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              ref.read(countProvider.notifier).increment();
            },
            child: const Icon(Icons.add),
          ),
          const SizedBox(width: 8),
          FloatingActionButton(
            onPressed: () {
              ref.read(countProvider.notifier).decrement();
            },
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}
```
- **CountNotifier:**
    - `CountNotifier` は `StateNotifier` を継承し、整数の状態を管理するクラスです。
    - コンストラクタで初期値 0 を指定しています。
    - `increment` は状態を増やし、 `decrement` は状態を減らすメソッドです。

- **countProvider:**
    - `StateNotifierProvider` を使用して、 `CountNotifier` のインスタンスを提供するプロバイダーを作成しています。このプロバイダーは `CountNotifier` の状態を取得するために使用されます。

- **MyHomePage:**
    - `ConsumerWidget` を継承し、プロバイダーの状態を監視するためのウィジェットです。 `ref.watch(countProvider)` で `countProvider` の状態を取得しています。また、 `ref.read(countProvider.notifier)` で `CountNotifier` のメソッドにアクセスしています。
    - ボタンが押されると、 `increment` と `decrement` それぞれのメソッドが呼び出され、状態が変更されます。
    - `final count = ref.watch(countProvider);` で状態を監視しているため、ボタンが押されて状態が変更されるたびに、Textが更新されていきます。


## 主なProvider
|名前|概要|
|---|---|
|Provider|簡単な状態を提供する最も基本的なプロバイダであり、値を同期的に生成する|
|StateProvider|外部から変更が可能な状態を公開するプロバイダ、ステートの管理にわざわざ StateNotifier クラスを定義するほどではない場合に使用する。|
|StateNotifierProvider|StateNotifierを監視し、公開するためのプロバイダ|
|ChangeNotifierProvider|ChangeNotifier を Flutter で利用するためのプロバイダ、Riverpod では使用を非推奨|
|FutureProvider|Futureをラップした非同期操作可能なProvider 非同期で取得した値を提供する|
|StreamProvider|Streamをラップした非同期操作可能なProvider 断続的に最新の値を提供する|

### Provider 
外部から変更することができないデータを管理する基本的な Provider です。状態の変更通知などは自動的に行われず、単なるデータの提供に焦点が当てられています。

計算結果をキャッシュしたり、変更することのないデータを渡す際などに使用されます。キャッシュの例でいくと、フィルタリング機能が挙げられます、データにフィルタを適用する場合、画面が再描画される度にフィルタを適用すると余計な処理が入ってしまいます。そこで、 `Provider` を利用することで、フィルタリングしたデータをキャッシュさせることができ、都度処理が呼ばれるのを防ぐことができるのです。

#### サンプルコード
```dart
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 const MaterialApp(
      title: 'Riverpod Counter',
      home: MyHomePage(),
    );
  }
}

final strProvider = Provider((ref) {
  return 'Hello Riverpod';
});

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final value = ref.watch(strProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod Sample'),
      ),
      body: Center(
        child: Text(
          value,
          style: const TextStyle(
              fontSize: 24
          ),
        ),
      ),
    );
  }
}
```
- `strProvider` は、 `Riverpod` の `Provider` を使用して定義されています。このプロバイダーは文字列 'Hello Riverpod' を返しています。

- `MyHomePage` クラスは、 `ConsumerWidget` を継承し、 `build` メソッドでUIを構築しています。 `strProvider` の値を `ref.watch` で取得し、それをテキスト表示しています。

### StateProvider
外部から変更することができるデータを管理するProviderです。StateNotifierProvider の簡易版で、ステートの管理に `StateNotifier` クラスを定義するほどではないシンプルなステートを管理する場合に使用します。

外部からデータを変更する時は `StateController` インスタンスの `update()` 、または直接 `state` に変更を加えることでデータを変更します。

**シンプルなステートを管理する場合**
- 列挙型（enum）、フィルタの種類など
- 文字列型、テキストフィールドの入力内容など
- bool 型、チェックボックスの値など
- 数値型、ページ数やフォームの年齢など

**StateProviderを使用すべきではないステート**
- ステートの算出に何かしらのバリデーション（検証）ロジックが必要な場合
- ステート自体が複雑なオブジェクトである場合（カスタムのクラスや List/Map など）

上記のような複雑なステートを管理する場合は、 `StateNotifierProvider`　を使用することが推奨されています。

#### サンプルコード
```dart
import 'package:collection/collection.dart';
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({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class Product {
  Product({required this.name, required this.price});

  final String name;
  final double price;
}

final _products = [
  Product(name: 'iPhone', price: 999),
  Product(name: 'cookie', price: 2),
  Product(name: 'ps5', price: 500),
];

enum ProductSortType {
  name,
  price,
}

final productSortTypeProvider = StateProvider<ProductSortType>(
  (ref) => ProductSortType.name,
);

final productsProvider = Provider<List<Product>>((ref) {
  final sortType = ref.watch(productSortTypeProvider);
  switch (sortType) {
    case ProductSortType.name:
      return _products.sorted((a, b) => a.name.compareTo(b.name));
    case ProductSortType.price:
      return _products.sorted((a, b) => a.price.compareTo(b.price));
  }
});

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final products = ref.watch(productsProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
        actions: [
          DropdownButton<ProductSortType>(
            value: ref.watch(productSortTypeProvider),
            onChanged: (value) =>
                ref.read(productSortTypeProvider.notifier).state = value!,
            items: const [
              DropdownMenuItem(
                value: ProductSortType.name,
                child: Icon(
                  Icons.sort_by_alpha,
                  color: Colors.black,
                ),
              ),
              DropdownMenuItem(
                value: ProductSortType.price,
                child: Icon(
                  Icons.sort,
                  color: Colors.black,
                ),
              ),
            ],
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('${product.price} \$'),
          );
        },
      ),
    );
  }
}
```
- **Product クラス:**
    - `Product` クラスは商品を表すデータモデルです。商品には名前（name）と価格（price）が含まれています。

- **_products リスト:**
    - `_products` リストには、 `Product` クラスのインスタンスが複数含まれています。これはサンプルの商品データです。

- **ProductSortType:**
    - `ProductSortType` は、商品のソート方法を示すために使用されます。 `name` の場合は名前でソートし、 `price` の場合は価格でソートします。

- **productSortTypeProviderプロバイダー:**
    - `productSortTypeProvider` は、商品をソートする方法（名前か価格か）を監視します。

- **productsProvider プロバイダー:**
    - `productsProvider` は、現在のソート方法に基づいて商品リストを返しています。

- **MyHomePage ウィジェット:**
    - `MyHomePage` クラスは `ConsumerWidget` を継承し、 `build` メソッドでUIを構築しています。商品リストが表示され、AppBarには商品をソートするドロップダウンメニューがあります。
    - 商品リストは `ListView.builder` を使用して構築され、各商品は `ListTile` で表示されます。

- **ドロップダウンメニュー `onChanged`**

```dart
onChanged: (value) =>
    ref.read(productSortTypeProvider.notifier).state = value!,
```

- `read` メソッドで、 プロバイダーの値を読み取り、 `productSortTypeProvider.notifier`で、 `StateProvider` の更新メソッドにアクセスし、 `state = value!` で状態を更新しています。

上記のように `state` を使用して新たなステートを作成することもできますが、 **現在の状態をパラメータとして利用できること、状態のイミュータブルな更新が保証されること**から `update` メソッドを使うことが推奨されています。

上記のコードを `update` メソッドを使用して書くと以下のようになります。

```dart
onChanged: (value) =>
    ref.read(productSortTypeProvider.notifier).update(
          (state) => state = value!,
        ),
```

### StateNotifierProvider
`StateNotifier` を監視し、データとして管理するためのプロバイダです。変化するステートを管理するソリューションとして推奨されています。

一般的には以下のような場面で使用されます。
- イミュータブル（不変）なステートを管理する場合 (イミュータブルではあるが、イベントに応じて状態が変わることがある場合）。
- ステートを変更するためのロジックを一つの場所で集中管理する場合。

以下のサンプルコードは、 `StateNotifierProvider` を使用して Todo リストを作成しています。 `StateNotifierProvider` を使うことで `addTodo` などのメソッドをUI 側に渡して Todo リストの内容を操作できるようになります。
#### サンプルコード
```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'main.freezed.dart';

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

@freezed
class Todo with _$Todo {
  factory Todo({
    required String text,
  }) = _Todo;
}

class TodoListNotifier extends StateNotifier<List<Todo>> {
  TodoListNotifier() : super([]);

  void addTodo(String text) {
    state = [...state, Todo(text: text)];
  }

  void removeTodo(int index) {
    state = List.from(state)..removeAt(index);
  }
}

final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>(
      (ref) => TodoListNotifier(),
);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo List',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  MyHomePage({Key? key}) : super(key: key);

  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(todoListProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Todo List'),
      ),
      body: ListView.builder(
        itemCount: state.length,
        itemBuilder: (context, index) {
          final todo = state[index];
          return ListTile(
            title: Text(todo.text),
            trailing: IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () {
                ref.read(todoListProvider.notifier).removeTodo(index);
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddTodoDialog(context, ref),
        child: const Icon(Icons.add),
      ),
    );
  }

  Future<void> _showAddTodoDialog(BuildContext context, WidgetRef ref) async {
    return showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Add Todo'),
          content: TextField(
            controller: _textController,
            decoration: const InputDecoration(
              hintText: 'Enter your todo',
            ),
          ),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
                _textController.clear();
              },
              child: const Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                final newText = _textController.text.trim();
                if (newText.isNotEmpty) {
                  ref.read(todoListProvider.notifier).addTodo(newText);
                }
                Navigator.of(context).pop();
                _textController.clear();
              },
              child: const Text('Add'),
            ),
          ],
        );
      },
    );
  }
}
```
- **Todoクラス:**
    - Todo クラスは[Freezedパッケージ](https://pub.dev/packages/freezed)によって生成された不変のデータクラスです。 `@freezed` アノテーションを使用して Freezed クラスとして宣言されています。
    - `_$Todo` は、Freezedが生成するファクトリーコンストラクタの名前です。 `_$Todo` の$はアンダースコアに続く文字を大文字に変換するFreezedの慣習です。
    - `factory` キーワードを使ってコンストラクタを定義しており、 `required` で `text` パラメータを受け取る `Todo()` コンストラクタが作成されています。
    - `_Todo` クラスは Freezed によって生成される、不変のデータクラスです。このクラスのインスタンスは一度作成されたら変更できません。これにより、不変性が確保され、安全性が向上します。



- **TodoListNotifierクラス:**
    - `TodoListNotifier` クラスは `StateNotifier` を継承しており、 `List<Todo>` の状態を管理しています。
    - `state` プロパティは `StateNotifier` クラスが管理する現在の状態を表しており、 `state =` で、新しい状態を指定することができます。
    - `addTodo` メソッドの `[...state, Todo(text: text)]` は、スプレッド演算子 (...) を使用して、現在の状態 (state) の要素を新しいリストに展開し、その後 Todo オブジェクトを追加しています。
    - `removeTodo` メソッドの `List.from(state)` は、現在の状態を新しいリストに複製しています。この複製を作成することで、元のリストに変更が加えられても元のリスト自体は変更されないため、不変性が保たれます。 また、 `..removeAt(index)` で、複製されたリストから指定された index の位置にある要素を削除しています。このカスケード演算子 (..) は、同じオブジェクトに対して複数の操作を行うために使用されます。

- **todoListProvider:**
    - `StateNotifierProvider` では、 `TodoListNotifier` を使用して List<Todo> の状態を提供しているため、アプリケーション内の異なる部分で同じTodoリストを共有することができます。
    - `StateNotifierProvider` のジェネリクス型引数は、2つの型引数を取ります。第一引数 (TodoListNotifier) は、状態を管理する `StateNotifier` の型です。第二引数 (List<Todo>) は、`StateNotifier` が管理する状態の初期値の型です。
    - `(ref) => TodoListNotifier()` は、Providerが初めて読み込まれたときに呼び出される初期化関数です。


- **MyHomePageウィジェット:**
    - `MyHomePage` クラスは `ConsumerWidget` を継承し、ウィジェットツリー内で `todoListProvider` を監視しています。
    - `ref.watch` と `ref.read` を使用して、Todoリストの表示やTodoの追加・削除の機能を実装しています。

### ChangeNotifierProvider
ChangeNotifierProvider は `ChangeNotifier` を Flutter で利用するためのプロバイダです。Riverpod では以下の理由により、使用を非推奨にしています。可能な限り `StateNotifierProvider` を使用し、 `ChangeNotifierProvider` の使用はミュータブルなステート管理を行う必然性がある場合に限定してください。

- `package:provider` で `ChangeNotifierProvider` を利用していた際の移行作業を容易にするため
- ミュータブル（可変）なステート管理をサポートするため

データの更新を監視元に伝えたい時は、 `notifyListeners()` を実行する必要があります。`notifyListeners()` を記載していないと、データを更新しても監視元に伝わらないので注意してください。

#### サンプルコード
```dart
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 const MaterialApp(
      title: 'Todo List',
      home: MyHomePage(),
    );
  }
}

class Counter extends ChangeNotifier {
  int counter = 0;

  void increment() {
    counter++;
    notifyListeners();
  }
}

final counterProvider = ChangeNotifierProvider((ref) {
  return Counter();
});

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod Sample'),
      ),
      body: Center(
        child: Text(
          state.counter.toString(),
          style: const TextStyle(fontSize: 24),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: ref.read(counterProvider).increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}
```
- **Counterクラス:**
    - `ChangeNotifier` を継承した `Counter` クラスがあります。 `Counter` クラスは、カウンターの値を保持し、 `increment` メソッドを使用して値を増やし、変更があった場合にリスナーに通知します。

- **ChangeNotifierProvider:**
    - `ChangeNotifierProvider` は、 `Counter` クラスのプロバイダーを作成しています。これにより、 `Counter` クラスの状態が変更されたときにウィジェットツリーがリビルドされるようになります。

- **MyHomePageウィジェット:**
    - `MyHomePage` は `ConsumerWidget` を継承しており、 `ref.watch(counterProvider)` を使用して `Counter` の状態を監視し、 `ref.read(counterProvider)` を使用して `Counter` のメソッドを呼び出しています。
    - カウンターの値を表示するTextウィジェットがあります。この値は `state.counter` から取得され、 `Counter` の状態に変更があると自動的に更新されます。
    - `FloatingActionButton` をタップすると、 `controller.increment` が呼び出されてカウンターが増加し、UIが更新されます。

### FutureProvider
FutureProvider は非同期操作が可能な Provider で、 `Future` から取得したデータを管理する際に使用します。

主に以下のような用途で使われます。

- 非同期操作を実行し、その結果をキャッシュするため。（ネットワークリクエストなど）
- 非同期操作の error状態、loading状態を適切に処理するため。
- 非同期的に取得した複数の値を組み合わせて一つの値にするため。

FutureProvider は `ref.watch` を使用することで、何かの値が変わったときに自動でデータを再取得するよう設定でき、プロバイダが常に最新データを渡すことを保証できます。

#### サンプルコード
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Future<String> fetchData() async {
  await Future.delayed(const Duration(seconds: 2)); 
  return 'Hello, Riverpod!';
}

final futureProvider = FutureProvider<String>((ref) => fetchData());

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'FutureProvider Sample',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final AsyncValue<String> data = ref.watch(futureProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('FutureProvider Sample'),
      ),
      body: Center(
        child: data.when(
          loading: () => const CircularProgressIndicator(),
          error: (error, stack) => Text('Error: $error'),
          data: (value) => Text(
            value,
            style: const TextStyle(fontSize: 24),
          ),
        ),
      ),
    );
  }
}
```
- **fetchData関数:**
    - `fetchData` メソッドは非同期で2秒待機した後、'Hello, Riverpod!'という文字列を返す関数です。

- **FutureProvider:**
    - `FutureProvider` は非同期データを提供するためのProviderです。この場合、 `Future<String>` 型の非同期関数である `fetchData` を使用して非同期データを提供しています。

- **MyHomePageクラス:**
    - `MyHomePage` は `ConsumerWidget` を継承しています。
    - `ref.watch(futureProvider)` を使用して非同期データを取得し、 `AsyncValue` のwhenメソッドを使用して非同期データの状態に応じてUIを構築しています。ローディング中は `CircularProgressIndicator` が表示され、エラーが発生した場合はエラーメッセージが表示され、データが利用可能な場合はそのデータが表示されます。
    - `when()` のdata 引数には非同期処理が完了した時に表示するウィジェットを返すコールバック関数、loading 引数には非同期処理を実行している時に表示するウィジェットを返すコールバック関数、error 引数には非同期処理に失敗した時に表示するウィジェットを返すコールバック関数をそれぞれ指定します。
    - **error:** エラーの詳細情報が含まれるオブジェクトです。通常、これはException型のオブジェクトですが、FutureProviderが提供するエラーには他にも様々な型があります。エラーが何であるかは非同期データを提供しているプロバイダーに依存します。例えば、通信エラーの場合はDioErrorなど。
    - **stack:** エラーが発生したときのスタックトレースが含まれるオブジェクトです。通常、デバッグやエラーのトラブルシューティングに使用されます。

### StreamProvider
StreamProvider は FutureProvider の Stream 版です。

一般的には以下のような用途で使われます。

- Firebase や WebSocket の監視するため。
- 一定時間ごとに別のプロバイダを更新するため。

Stream 自体が値の更新を監視する性質を持つため、 `StreamBuilder` で十分ではないかと思うかもしれません。しかし、 `StreamBuilder` の代わりに `StreamProvider` を使用することで以下の利点を得ることができます。

- 他のプロバイダで `ref.watch` を通じてストリームを監視することができる。   
- AsyncValue により loading/error のステートを適切に処理することができる。
- ストリームから出力された直近の値をキャッシュしてくれる。（途中で監視を開始しても最新の値を取得することができる）
- StreamProvider をオーバーライドすることでテスト時のストリームを簡単にモックすることができる。

#### サンプルコード
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Stream<String> fetchData() {
  return Stream.periodic(const Duration(seconds: 1), (value) {
    return 'Data $value from stream';
  });
}

final streamProvider = StreamProvider<String>((ref) => fetchData());

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'StreamProvider Sample',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final AsyncValue<String> data = ref.watch(streamProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('StreamProvider Sample'),
      ),
      body: Center(
        child: data.when(
          loading: () => const CircularProgressIndicator(),
          error: (error, stack) => Text('Error: $error'),
          data: (value) => Text(
            value,
            style: const TextStyle(fontSize: 24),
          ),
        ),
      ),
    );
  }
}
```

- **fetchData関数:**
    - `fetchData` 関数は、1秒ごとに新しいデータを生成する非同期なストリームを作成しています。
    - `Stream.periodic` は、一定の時間間隔で定期的に値を生成するストリームを作成するメソッドです。
    - `const Duration(seconds: 1)` で1秒ごとに新しい値を生成するように指定しています。

- **StreamProviderの定義:**
    - `StreamProvider` は非同期なストリームからデータを提供するための Provider です。この場合、 `fetchData` 関数を使用して非同期ストリームを提供しています。

- **MyHomePageクラス:**
    - `ConsumerWidget` を継承し、StreamProviderが提供する非同期なストリームを `ref.watch(streamProvider)` を使用して取得し、 AsyncValue のwhenメソッドを使用して非同期データの状態に応じてUIを構築しています。
    - データが読み込み中なら `CircularProgressIndicator` 、エラーが発生したらエラーメッセージ、データが利用可能ならそのデータが表示されます。

## Provider修飾子とメソッド
### family
外部からパラメータを渡してデータを作成するようにしてくれる修飾子です。 

作成したプロバイダに `.family` 修飾子を付けると、パラメータが追加され、追加されたパラメータはプロバイダのステート（状態）を計算する要素として使用することができます。

以下は`.family` 修飾子を使用する場面の例です。

- FutureProvider と family を組み合わせて、メッセージ ID から Message を取得する場合
- プロバイダにアプリの現在のロケール情報を渡して、翻訳文を取得する場合

```dart: メッセージ ID から Message を取得する例
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
  return dio.get('http://my_api.dev/messages/$id');
});
```

上記の family プロバイダをウィジェットなどで使用する場合、構文は通常のものと若干異なります。次の構文ではコンパイル時エラーが発生してしまいます。

```dart: コンパイルエラー発生
Widget build(BuildContext context, WidgetRef ref) {
  // エラー – messagesFamily はプロバイダではありません
  final response = ref.watch(messagesFamily);
}
```

代わりに、次のように `messagesFamily` に引数を渡してください。

```dart
Widget build(BuildContext context, WidgetRef ref) {
  final response = ref.watch(messagesFamily('id'));
}
```

`.family` 修飾子を使用するプロバイダに、異なる引数を同時に渡すことも可能です。例えば以下のように `messagesFamily` プロバイダに異なるid情報を渡し、それぞれのresponseを取得することもできます。

```dart
Widget build(BuildContext context, WidgetRef ref) {
  final response = ref.watch(messagesFamily('id'));
  final response1 = ref.watch(messagesFamily('id1'));
}
```
`.family` 修飾子はプロバイダに複数のオブジェクトを渡すことができません。
しかし、プリミティブ型（bool/int/double/String）か、定数（プロバイダ）もしくは `==` と hashCode をオーバーライドしたイミュータブル（不変）オブジェクトのいずれかである限りは どのような オブジェクトでも渡すことができます。以下はサンプルコードです。

#### サンプルコード
```dart
@freezed
abstract class MyParameter with _$MyParameter {
  factory MyParameter({
    required int userId,
    required Locale locale,
  }) = _MyParameter;
}

final exampleProvider = Provider.autoDispose.family<Something, MyParameter>((ref, myParameter) {
  print(myParameter.userId);
  print(myParameter.locale);
});

@override
Widget build(BuildContext context, WidgetRef ref) {
  int userId; // ユーザ ID をどこかで取得する
  final locale = Localizations.localeOf(context);

  final something = ref.watch(
    exampleProvider(MyParameter(userId: userId, locale: locale)),
  );

  ...
}
```
### autoDispose
参照されなくなった Provider のデータを破棄してくれる修飾子です。

Firebase 使用時に、サービスとの接続を切って不必要な負荷を避けたり、ユーザが別の画面に遷移してまた戻って来る際に、ステートをリセットしてデータ取得をやり直す際などに使用されます。

以下のコードは `.autoDispose` の使用例です。 `.autoDispose` 修飾子は他の修飾子と組み合わせることもできます。
```dart: autoDispose使用例
final userProvider = StreamProvider.autoDispose.family<User, String>((ref, id) {
 ...
});
```

### ref.keepAlive
プロバイダに `.autoDispose` 修飾子を付けると、 `ref` オブジェクトに `keepAlive` というメソッドが追加されます。

`keepAlive` メソッドを実行することで、プロバイダが参照されなくなった際にもステートを維持するように設定することができます。

例えば、以下のように HTTP リクエスト完了後に `keepAlive` を実行するとします。

```dart
final myProvider = FutureProvider.autoDispose((ref) async {
  final response = await httpClient.get(...);
  ref.keepAlive();
  return response;
});
```

すると、リクエスト完了前に画面を破棄して再度同じ画面に戻った場合は HTTP リクエストが再実行される一方、 リクエスト完了後に同じ動作を行った場合はステートが維持されるため、HTTP リクエストが再実行されることはありません。

### ref.onDispose
`ref.onDispose` を使用すると、プロバイダが参照されなくなったタイミング（プロバイダのステートを監視するオブジェクトがなくなったタイミング）で 処理を追加することができます。以下はリクエスト完了前に画面を離れてステートが破棄されたら、HTTP リクエストをキャンセルするサンプルコードです。

```dart: ref.onDisposeサンプルコード
final myProvider = FutureProvider.autoDispose((ref) async {
  // http リクエストのキャンセルを実行するための package:dio のオブジェクト
  final cancelToken = CancelToken();
  // プロバイダのステートが破棄されたら http リクエストをキャンセル
  ref.onDispose(() => cancelToken.cancel());

  // データを取得しつつキャンセル用の `cancelToken` を渡す
  final response = await dio.get('path', cancelToken: cancelToken);
  // リクエストが成功したらステートを維持する
  ref.keepAlive();
  return response;
});
```

### Consumer と HookConsumer ウィジェット
ref オブジェクトは `Consumer` もしくは `HookConsumer` ウィジェットのコールバック関数 `builder` から取得することもできます。

これらのウィジェットを使用する場合は `ConsumerWidget` または `HookConsumerWidget` のようにクラスを定義する必要がありません。以下は `HookConsumer` を使用した例になります。

```dart: HookConsumer使用例
Scaffold(
  body: HookConsumer(
    builder: (context, ref, child) {
      final state = useState(0);
      final counter = ref.watch(counterProvider);
      return Text('$counter');
    },
  ),
);
```

### ref.select
プロバイダを監視する対象を限定したい場合に使用します。例えば、特定の状態しか更新しない場合などに使用することで、不要な更新を避けることができます。

プロバイダを監視するということは、そのプロバイダが公開するオブジェクト全体の状態を監視するということです。 しかし、その監視の範囲を狭めて特定のプロパティのみを監視対象としたい場合があります。

例えば、以下のような `User` オブジェクトを公開するプロバイダがあるとします。

```dart
abstract class User {
  String get name;
  int get age;
}
```

それを監視するウィジェット側は `user` の `name` プロパティしか使用しません。

```dart
Widget build(BuildContext context, WidgetRef ref) {
  User user = ref.watch(userProvider);
  return Text(user.name);
}
```

上記のように `userProvider` を普通に監視すると、 無関係な `age` プロパティの変化もウィジェット更新のトリガーとなってしまいます。監視対象を `name` プロパティに限定したい場合は、 `select` を使用してその旨を明示的に宣言します。変更後のコードは以下のようになります。

```dart
Widget build(BuildContext context, WidgetRef ref) {
  String name = ref.watch(userProvider.select((user) => user.name));
  return Text(name);
}
```

### ref.read
取得時点での状態を取得するのに使用します。状態の監視は行いません。
`onTap` や `initState` などのイベント処理でメソッドを使用する際などに使われます。

#### `ref.read` は `build` メソッドで使用しない
ウィジェットのパフォーマンスを最適化するため、 `ref.read` を以下のように使うことがあるかもしれません。

```dart
final counterProvider = StateProvider((ref) => 0);

Widget build(BuildContext context, WidgetRef ref) {
  final counter = ref.read(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.state++,
    child: const Text('button'),
  );
}
```

しかし上記のようなコードは**追跡困難なバグの要因**になり得ます。プロバイダが公開する値は変わることがないため、 `ref.read` の使用は安全であると思うかもしれませんが、 **常にプロバイダの値が変わらないということは保証できません。**

変わらないと考えられていた値が、変更された場合、値の取得に `ref.read` を使っていた部分をすべて `ref.watch` に変更する必要があります。これはエラー発生の原因になり、変更し忘れる箇所がいくつか出てくる可能性もあります。

一方、最初から `ref.watch` を使用していれば、リファクタリング時に生じる問題は比較的少なくなるため、ほとんどの場面では `watch` や `listen` の使用、特に `watch` の使用がベターだと考えられています。

ウィジェットの更新回数を抑えるために `ref.read` を使いたい場合であっても、 `ref.watch` を使用してまったく同じ効果を得ることが可能です。

例えば
```dart
final counterProvider = StateProvider((ref) => 0);

Widget build(BuildContext context, WidgetRef ref) {
  StateController<int> counter = ref.read(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.state++,
    child: const Text('button'),
  );
}
```

とする代わりに、

```dart
final counterProvider = StateProvider((ref) => 0);

Widget build(BuildContext context, WidgetRef ref) {
  StateController<int> counter = ref.watch(counterProvider.notifier);
  return ElevatedButton(
    onPressed: () => counter.state++,
    child: const Text('button'),
  );
}
```

としてください。 この2つのコードの効果はいずれも同じであり、カウンターの数字が増えた際にボタンが更新されることはありません。加えて、後者のアプローチの場合はカウンターの数字がリセットされた場合にも対応することができます。 

例えば、以下のコードように別の場所で `ref.refresh` メソッドを使用して `StateController` オブジェクトを再度生成するとします。

もしここで `ref.read` を使用していた場合、ボタンは古い `StateController` インスタンスを使うことになり、このインスタンスはこの時点で既に破棄されているため、使うべきではないはずです。 一方 `ref.watch` を使用した場合は、ボタンが更新されて新しい `StateController` を取得することができます。

```dart
ref.refresh(counterProvider);
```

### ref.watch
状態を取得し、監視を続けるために使用します。値に変更があれば、画面更新が行われるため、Widgetにリアクティブな値を埋め込む場合などに使用します。

### ref.listen
`ref.watch` と同様にプロバイダを監視することができます。

最も大きな違いは、 `ref.watch` が値の変化に応じてウィジェットやプロバイダを更新するのに対して、 `ref.listen` は任意の関数を呼び出すことができます。エラー発生時のスナックバー表示など、何かしらの変化に反応して処理を実行したいときに使用します。

`ref.listen` メソッドは第1引数にプロバイダ、第2引数にステート（状態）が変化した際に実行するコールバック関数を設定します。 このコールバック関数には呼び出し時に、プロバイダの直前のステートと新しいステートの値が渡されるため、それぞれをパラメータとして使用できます。以下は listen メソッドを使用したサンプルコードです。

```dart
final counterProvider = StateNotifierProvider<Counter, int>(Counter.new);

class HomeView extends ConsumerWidget {
  const HomeView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen<int>(counterProvider, (int? previousCount, int newCount) {
      print('The counter changed $newCount');
    });
    
    return Container();
  }
}
```

## まとめ

### Riverpodとは
RiverpodはFlutterアプリケーションのための状態管理ライブラリで、柔軟性と拡張性が向上したProviderパッケージの改良版です。グローバルに定義されたプロバイダーを使用して、ウィジェットツリー内でデータを渡します。

### 基本的な使い方

#### 1. ProviderScopeをルートに追加:
Riverpodを使用する際は、まず初めに ProviderScope をアプリのルートで追加します。

#### 2. Providerをグローバル変数として定義:
Riverpodのプロバイダーはグローバルに定義され、手動で定義するかコード生成を使用して作成します。

#### 3. Providerからデータを取得する:
ConsumerWidget を使用して、 `build()` メソッド内で `WidgetRef` 型の引数を利用してプロバイダーからデータを取得します。

### 便利な機能

- **スコープの明確化:**
Riverpodはプロバイダーのスコープを厳密に定義し、異なるスコープを持たせることができるため、依存関係の制御が容易になっています。

- **リアクティブなデータ変更:**
リアクティブな変更通知をサポートしているため、データが変更された際に関連するウィジェットを自動的に再構築することができます。

- **様々なProvider:**
Providerだけでなく、FutureProvider、StreamProviderなど異なるデータソースに対する様々なプロバイダーが提供されています。

- **Null Safety:**
Null Safetyをサポートしているため、安全性と安定性が確保されています。


## 参考資料

https://www.flutter-study.dev/firebase-app/riverpod

https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/how-to-choose-a-riverpod

https://flutter.salon/plugin/riverpod/

https://zenn.dev/naoya_maeda/articles/a8bbf40a202c74

https://note-tmk.hatenablog.com/entry/2023/09/20/114412

https://riverpod.dev/ja/