Provider (Flutter Roadmap Provider)

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

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

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

## Roadmapとは

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

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

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

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

- provider: https://pub.dev/packages/provider
- Simple app state management: https://docs.flutter.dev/data-and-backend/state-mgmt/simple

## Providerとは
Provider とは、Flutterアプリケーションで状態管理を行うためのパッケージです。特に ChangeNotifier などの状態オブジェクトをウィジェットツリーに提供するために利用されます。Provider を使用することで、状態変更を検知してUIを更新するといった作業が簡単になります。

### Providerのメリット

- **簡潔なコード構造:** UIとデータのバインディングを容易に行うことができるため、状態管理のためのコードが簡潔になります。

- **依存性の注入:** アプリケーションの各部分で使用するデータを、依存性の注入を通じて渡すことができるため、特に大規模なアプリケーションでは、データの一貫性を確保しやすくなります。

- **プロバイダーの統一:** ChangeNotifierProvider、FutureProvider、StreamProvider など、異なるタイプのプロバイダーを提供しているため、異なるデータのタイプに対して統一的な手法で状態管理を行えます。

- **再利用性の向上:** Provider を利用することで、ウィジェットツリー内での再利用が容易になります。データの提供や更新に関するロジックをカプセル化し、再利用可能なコンポーネントを構築できます。

### Provider.value<T>() Provider.of<T>()
Widget ツリーの中に `Provider.value<T>()` を挟んでおくことで、それ以下の Widget から値を取得できるようになります。Tには共有したいデータの型を指定します。

`Provider.of<T>(context)` は Widget ツリー内のどこからでもProviderで提供されたデータにアクセスすることができます。Tには参照したいProviderの型を指定します。 

`Provider.of(context)` は内部では `InheritedWidget` の `inheritFromWidgetOfExactType` メソッドを使っているため、 `initState` より後でしか呼ぶことができません。

まとめると `Provider.value()` でデータを渡し、 `Provider.of()` はそのデータを受けとるために使用します。この二つのメソッドを組み合わせることで、Flutterアプリ内で状態を簡単に管理できるようになります。

`Provider.value<T>()`の他にも`Provider<T>()`を使用する方法があります。
```dart: value と create
Provider(
  create: (_) => UserRepository(),
)

Provider.value(
  value: UserRepository(),
)
```

どちらを選択するかは使用ケースに依存しますが、既にインスタンスがある場合は `.value` を使用し、都度新しいインスタンスを生成する必要がある場合は通常のコンストラクタを使用します。

既に生成されたインスタンスを提供するため、`.value` の方が再利用性が高く、処理が最小限に抑えられます。


### context.read() / context.watch()
`context.read()` と `context.watch()` は、 Providerパッケージで状態を読み込むためのメソッドです。

- **`context.read<T>()`:** 
    - Providerの現在の値を読み込むだけです。変更通知を購読しないため、状態変更時に自動的に再ビルドされることはありません。
    - 値の変更が関係なく、一度だけデータを取得したい場合に使用します。
    - 変更があってもウィジェットは再構築されません。

- **`context.watch<T>()`:** 
    - Providerの現在の値を読み込み、同時に変更通知を購読するため、状態が変更されるたびにWidgetを再ビルドします。
    - 変更通知が発生するとウィジェットが再構築されます。
    - `context.read<T>()`との違いは再ビルドするかしないかです。
    - ウィジェットの再構築をトリガーするときに、関連するウィジェットのビルドをトリガーすることになりますが、 `Provider.of()` は関連するすべてのウィジェットを再構築するため、 `context.watch()` の方が最適化された再構築が行えます。

### MultiProvider
Provider を複数使いたいときに使用します。 

`MultiProvider` を使用しない場合、個別に `Provider` を定義する必要があり、以下のようにネストした `Provider` を定義することで、 `MyApp` 以下のWidgetツリーから `UserRepository` と `ProductRepository` にアクセスできるようになります。

しかし、この方法ではコードが冗長になり、Providerが増えるたびにコードの可読性が低下します。

`MultiProvider` を使用することで、複数の `Provider` をまとめて定義することができ、コードはシンプルかつ可読性が高くなるのです。

```dart: MultiProviderを使用しなかった場合
Provider(
  create: (_) => UserRepository(),
  child: Provider(
    create: (_) => ProductRepository(), 
    child: MyApp()
  )
)
```

```dart: MultiProviderを使用した場合
MultiProvider(
  providers: [
    Provider(create: (_) => UserRepository()), 
    Provider(create: (_) => ProductRepository()),
  ],
  child: MyApp() 
)
```

## 主な Provider
|名前|説明|
|---|---|
|Provider|シンプルな値やオブジェクトを提供するための基本的なプロバイダーです。create コンストラクタを使用してインスタンスを生成します|
|ChangeNotifierProvider|ChangeNotifier クラスを使用したプロバイダーで、状態が変更されたときに通知され、UIの再構築をトリガーします|
|ListenableProvider|Listenable クラスを使用したプロバイダーで、変更をリッスンすることができます。Listenable クラスは `notifyListeners` メソッドを持つクラスです|
|ProxyProvider|他のプロバイダーの値を基にして新しい値を生成するためのプロバイダーです。変更が検知されたときに、新しい値が計算されます|
|StreamProvider|非同期処理である Stream の結果を提供するためのプロバイダーです。Stream が新しいデータを生成するたびに通知されます|
|FutureProvider|非同期処理である Future の結果を提供するためのプロバイダーです。非同期処理が完了すると、その結果が提供されます|

### Provider と ChangeNotifierProvider の違い
基本、`ChangeNotifier` を継承したクラスの受け渡しを行う場合は `ChangeNotifierProvider` を使い、それ以外のデータを受け渡す時は `Provider` を使用します。
- **Provider：** 単にデータのみ受け渡せれば良い時に使用する、**データは更新されない。**
- **ChangeNotifierProvider：** データの受け渡しだけでなくデータの更新も行いたい場合に使用する。

## ProxyProvider 
`ProxyProvider` とは、 `provider` パッケージで提供されているプロバイダーの一種です。このプロバイダーは、他のプロバイダーからの変更を受けて新しい値を生成するとき、例えば、ログイン前とログイン後で表示する記事一覧を変えたい場合などに使用されます。

`ProxyProvider` を使用すると、新しい値の計算を行う際に他のプロバイダーの値に依存できるため、異なるプロバイダー間でのデータの依存関係を効果的に管理できます。

以下の例では、 `ProxyProvider` を使用して `DependencyProvider` の変更に基づき`CounterModel` を提供しています。初回のみ `DependencyProvider` の値から `CounterModel` を作成し、それ以降は前回の結果を再利用します。

"Update Dependency" ボタンを押すと、 `DependencyProvider` の `dependencyValue` が更新され、これに基づいて `CounterModel` も再計算されます。

### サンプルコード

```dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterModel {
  int count;

  CounterModel(this.count);
}

class DependencyProvider extends ChangeNotifier {
  int dependencyValue = 42;

  void updateDependencyValue(int newValue) {
    dependencyValue = newValue;
    notifyListeners();
  }
}

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => DependencyProvider()),
        ProxyProvider<DependencyProvider, CounterModel>(
          update: (_, dependency, previous) =>
              previous ?? CounterModel(dependency.dependencyValue),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterModel = Provider.of<CounterModel>(context);
    final dependencyProvider = Provider.of<DependencyProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('ProxyProvider Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Counter: ${counterModel.count}'),
            Text('Dependency: ${dependencyProvider.dependencyValue}'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                counterModel.count += 1;
              },
              child: Text('Increment Counter'),
            ),
            ElevatedButton(
              onPressed: () {
                dependencyProvider.updateDependencyValue(99);
              },
              child: Text('Update Dependency'),
            ),
          ],
        ),
      ),
    );
  }
}
```
## StreamProvider
`StreamProvider` は、 `provider` パッケージで提供されているプロバイダーの一つで、主に非同期のデータを扱う際に使用されます。
- **非同期データの表示:** リアルタイムなアップデートがあるデータなどバックエンドからの非同期データを表示する際に使用されます。

- **リアルタイムなUI更新:** Stream がデータを提供すると、そのデータが変更されるたびにUIが自動的に更新されます。

- **非同期データのプロバイダー:** Stream からデータを非同期に取得し、それをアプリケーション内の他の部分で利用可能にします。

### StreamProviderの主なプロパティ
- **create:** Stream を生成するための関数を指定します。この関数は `Stream<T>` を返す必要があります。

- **initialData:** Stream がデータを返さない場合に使用される初期データを指定します。

- **catchError:** Stream でエラーが発生した場合に使用されるデータを指定します。

- **updateShouldNotify:** 新しいデータが古いデータと異なる場合にリビルドするかどうかを制御するためのコールバック関数です。通常は `(previous, next) => true` としておくと、新しいデータが来たときにリビルドが行われます。

### サンプルコード
```dart
import 'dart:async';

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

void main() {
  runApp(
    MaterialApp(
      home: StreamProvider<int>(
        create: (_) => counterStream(),
        initialData: 0,
        catchError: (_, error) => 0,
        updateShouldNotify: (prev, next) => true,
        child: const MyApp(),
      ),
    ),
  );
}

Stream<int> counterStream() async* {
  int count = 0;
  while (true) {
    await Future.delayed(const Duration(seconds: 1));
    yield count++;
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<int>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('StreamProvider Example'),
      ),
      body: Center(
        child: Text('Counter: $counter'),
      ),
    );
  }
}
```
このアプリケーションは、1秒ごとに増加する値をリアルタイムにUIに表示し、StreamProvider を使用して非同期データを効果的に扱う方法を示しています。

- `counterStream` 関数では、1秒ごとに値が増加する非同期ストリームを生成します。 `async*` キーワードを使用して非同期ジェネレーター関数を宣言しています。

- `await Future.delayed(const Duration(seconds: 1));` で1秒待っています。 `yield count++;` で現在のカウント値を Stream に送信し、カウントを増やしています。

- `StreamProvider` は、 Stream から非同期データを提供するためのものです。

- `create` パラメータは、非同期データを生成するための関数で、この例では `counterStream` 関数が1秒ごとに増加するカウンターの値を生成しています。

- `initialData` は、 Stream がデータを返さない場合に使用される初期データを指定します。この例では初期値を0に設定しています。

- `catchError` は、 Stream でエラーが発生した場合に使用されるデータを指定します。この例ではエラーが発生した場合に0を返すようにしています。

- `MyApp` ウィジェットでは、 `Provider.of<int>(context)` を使用して現在のカウンターの値を取得しています。この値は  `Text('Counter: $counter')` で表示されます。

## FutureProvider 
`FutureProvider` は、非同期でデータを生成する `Future` を提供するためのプロバイダーです。主に非同期の処理が終了した後にその結果をUIに渡す場合に使用されます。

- **非同期データの提供:** `FutureProvider` は非同期でデータを取得するために使用されます。例えば、リクエスト処理やデータベースクエリなどの非同期操作が完了した後に、その結果をUIに提供する場合に活用されます。

- **ローディングの管理:** データがまだ取得されていない場合や、取得中にローディング状態を表示する際に利用されます。

### FutureProviderの主なプロパティ

- **create:** 非同期処理を行う Future を返す関数を指定します。この関数は `Provider` が初めて読み込まれたときに呼び出され、その結果が提供されます。

- **initialData:** 非同期処理が完了する前に使用される初期データを指定します。非同期処理が完了すると、この初期データは無視されます。

- **catchError:** 非同期処理中にエラーが発生した場合に使用されるデータを指定します。これにより、エラーが発生した場合でもUIが正常に表示されるようになります。

- **updateShouldNotify:** `Provider` の値が変更されたときにUIを更新するかどうかを制御します。通常は Future の結果が変化するので、変更を通知するためには `(_, __) => true` とします。

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

void main() {
  runApp(
    MaterialApp(
      home: FutureProvider<int>(
        create: (_) => fetchData(),
        initialData: 0,
        catchError: (_, error) => 0,
        updateShouldNotify: (_, __) => true,
        child: const MyApp(),
      ),
    ),
  );
}

Future<int> fetchData() async {
  await Future.delayed(const Duration(seconds: 2)); 
  return 42;
}

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

  @override
  Widget build(BuildContext context) {
    final int data = Provider.of<int>(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('FutureProvider Example'),
      ),
      body: Center(
        child: Text('Data from Future: $data'),
      ),
    );
  }
}
```
- **main 関数:**
    - `FutureProvider` を使用して、非同期データを提供するための `MyApp` をラップしています。

    - `create` プロパティは非同期関数 `fetchData` を指定しています。この関数は2秒後に値 `42` を返します。
    
    - `initialData` プロパティは非同期処理が完了する前に使用される初期データを指定しますこの例では0を返すように指定しています。
    
    - `catchError` プロパティは非同期処理中にエラーが発生した場合に使用されるデータを指定しますここでは0を返すように指定しています。
    
- **fetchData 関数:**
    - `fetchData` 関数は非同期で2秒待機し、その後に `42` を返す単純な非同期関数です。
    
- **MyApp クラス:**
    - MyApp クラスは `FutureProvider` から提供された非同期データを受け取り、UIに表示しています。
    
    - `Provider.of<int>(context)` を使用して非同期データにアクセスしています。

## まとめ
### Providerとは
Providerは、Flutterアプリ内で状態を管理するためのライブラリです。特に以下のようなメリットがあります。

- 簡潔なコード構造が実現できる
- データの依存性注入がしやすい
- 統一的な状態管理ができる
- コンポーネントの再利用性が向上する

ウィジェットツリーに状態を提供することで、UIとデータを簡単にバインドできるのが Provider の強みです。

### 基本的な使い方
Providerには `Provider.value()` と `Provider.of()` の2つの主要なメソッドがあります。

`Provider.value()` でデータを提供し、 `Provider.of()` でそのデータにアクセスします。この2つのメソッドを組み合わせることで、状態共有が実現できます。

またデータにアクセスするには別の方法も存在します。それが`context.read()`と`context.watch()`です。

データを取得する場合、 `context.read()` を使うと状態変更時に再ビルドされません。一方 `context.watch()` を使うと、状態変更時にウィジェットツリーを再ビルドします。

### 便利な機能

- `MultiProvider` を使うと、複数の Provider をまとめて定義できます。これによりコードの可読性が上がります。

- `ProxyProvider` は、既存の Provider から値を生成するカスタムProviderです。状態の変換などに利用できます。

- `StreamProvider` や `FutureProvider` を使うと、非同期処理の結果を簡単に状態として扱えます。

Providerは状態管理を抽象化し、簡潔で柔軟なコードを実現してくれます。大規模アプリ開発において重宝するライブラリのため、使い方を覚えることで、開発効率が大きく向上するはずです。

## 参考資料

https://www.flutter-study.dev/create-app/provider

https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9

https://itome.team/blog/2019/12/flutter-advent-calendar-day7/

https://qiita.com/chooyan_eng/items/9a16f6e54c13cb72a04e