【Flutter】 Providerで全域状態を管理する

# Providerとは?

- 全域で状態管理ができるようにしてくれるツール。

- Flutterでは状態を管理する様々な方法があるが、多様なパッケージの中でユーザーが最も多いパッケージ。

- [Provider github repository](https://github.com/rrousselGit/provider)

> Reactで言えばRedux、Recoilのようなツール

# なぜProviderを使うのか？
- コンポーネント間で状態を共有するためには、2つのコンポーネントを1つの親コンポーネントに包むプロセスが必要だ。

- 上記のような過程は、コンポーネントtreeのdepthが深まれば深まるほど不要なコンポーネントを増加させ、このような問題は性能イシューを呼び起こす恐れがある。 

- Providerは、depthに関係なくグローバルに状態を管理し、どこでもデータを簡単に使用できるようサポートしてくれる。


# Provider を使ってみよう

- Providerを使って簡単なcounterを実装してみよう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959845/97eabce5-0c6f-2508-d358-ba6c86973dfb.gif" width="300" height="300">



## Directory
```
├── lib
   ├── providers 
   │    ├── counts.dart
   ├── widgets 
   │    ├── buttons.dart
   │    ├── counter.dart
   └── main.dart
``` 

## Provider install
`flutter pub add provider`

## providers/counts.dart
- Provider 生成コード。

- `count`の状態を管理する。

- Provider は `ChangeNotifier` を[相続(多重相続)](https://dart.dev/language/mixins)して生成。

- 画面に更新されたデータを反映させるために `notifyListeners` を使用。

```dart
// counts.dart
// Provider 生成
import 'package:flutter/material.dart';

class Counts with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }

  void remove() {
    _count--;
    notifyListeners();
  }
}
```

## main.dart

- `MultiProvider`を使えば複数のProviderを追加して複数の状態を管理することができる。

- 1つの状態だけを管理するときは `ChangeNotifierProvider` を使用する。

- `ChangeNotifierProvider` を通じて `counts.dart` で作成した `Counts` を購読する。

- `buttons.dart` `counter.dart` ファイルでもProviderを使用できるように `ChangeNotifierProvider` で包んだ。

```dart
// main.dart
// Provider パッケージと上で生成したProviderインポートする
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
import 'package:provider_example/widgets/buttons.dart';
import 'package:provider_example/widgets/counter.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counts()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Test',
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider'),
      ),
      body: ChangeNotifierProvider(
        create: (BuildContext context) => Counts(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Counter(),
              Buttons(),
            ],
          ),
        ),
      ),
    );
  }
}
```

## widgets/button.dart

- ボタンを画面に表示するコード

- `main.dart` で `Buttons` コンポーネントを `ChangeNotifierProvider` で包み込んでいるため、contextを利用してProviderを使用することができる。

- `context.read<T>()` ➔ Tのデータ値を変更するなどのイベント用に使用

- `onPressed: () { context.read<Counts>().add(); }` ➔ 該当ボタンをクリックすると、countに+1をさせる.

- `onPressed: () { context.read<Counts>().remove(); }` ➔ 該当ボタンをクリックすると、countに-1をさせる。

```dart
// button.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';

class Buttons extends StatelessWidget {
  const Buttons({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().add();
            },
            child: const Icon(Icons.add)),
        const SizedBox(
          width: 40,
        ),
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().remove();
            },
            child: const Icon(Icons.remove))
      ],
    );
  }
}
```

## widgets/counter.dart

- 更新されたデータを表示するコード

- `context.watch<T>()` ➔ Tのデータ値を画面に表示する用途で使用

```dart
// counter.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
class Counter extends StatelessWidget {
  const Counter({super.key});
  @override
  Widget build(BuildContext context) {
    return Text(
      context.watch<Counts>().count.toString(),
      style: const TextStyle(
        fontSize: 20,
      ),
    );
  }
}
```

# 備考

- [Consumer](https://flutterbyexample.com/lesson/rebuilding-widgets-with-consumer) を使ったらもっと効率的なコード作成ができそう。

1. Consumerを使用すると、そのWidgetが依存するデータが変更された場合にのみ、そのWidgetをレンダリングする。 これにより、アプリ全体をレンダリングし直すのではなく、必要な部分だけを更新することでアプリの性能を向上させることができる。

2. Consumerを使用するとコードが読みやすくなる。Consumerは該当Widgetで使用するデータへの依存性を明示的に表現できる。 これにより、コードの可読性を高め、debugを容易にする。

```dart
# Consumer example

// provider.dart
class Counter extends ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();
  }
}

// counter.dart
class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Counter>(
      builder: (context, counter, child) {
        return Text(
          'Count: ${counter.count}',
        );
      },
    );
  }
}
```