Providerの使い方を理解する。

Flutter Advent Calendar#3 14日目です。

# Providerがピンとこない

`Provider`、状態管理という概念的にはなんとなくわかっても、巷に溢れているチュートリアルや解説を読んでも実装がいまいちがピンとこなかったので可能な限りシンプルに理解を試みてみました。
なお、`InheritedWidget`は使ったことがありません。`ScopedModel`も知りません。`BLoC`という単語もさっぱり。
（たぶんこのせいで理解が及んでいない）

環境は以下の通りです。

```
Flutter 1.22.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7891006299 (25 hours ago) • 2020-12-10 11:54:40 -0800
Engine • revision ae90085a84
Tools • Dart 2.10.4
```
Providerは、`Provider: ^4.3.2+3`となります。

# 素のプロジェクトをつくる

まず、プロジェクトを生成します。

```
$ flutter create myApp2Provider

$ cd myApp2Provider
$ flutter run
```

お馴染みの画面です。
![Simulator Screen Shot - iPhone 12 Pro Max - 2020-12-12 at 06.32.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4668/b4f5f0ef-a242-0bd8-9938-ec575c1fe7b9.png)

これからコードを弄りますが、画面は一切変わらないのでスクリーンショットはこれだけになります。

# Providerを導入

`pubspec.yaml`にパッケージを追加します。

```yaml:pubspec.yaml
dependencies:
  provider: ^4.3.2+3
```
vscodeだと自動的に、`flutter pub get`が走ってくれます。
まず、パッケージをインポート。

```dart:main.dart
import 'package:provider/provider.dart';
```

`ChangeNotifier`を継承してオブジェクトクラスを定義。

```dart:main.dart
class MyModel extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}
```

ここで元々、`_MyHomePageState`クラスにあった`_counter`の状態管理をします。
値を更新した時は、`notifyListeners()`を呼ぶようです。

`main()`のrunAppにて、最上位の`Widget`（ここではMyApp）を包む形で`ChangeNotifierProvider`を追加。
 
```diff:main.dart
 void main() {
-  runApp(MyApp());
+  runApp(ChangeNotifierProvider(create: (_) => MyModel(), child: MyApp()));
 }
```

`_MyHomePageState`で管理していた_counterをコメントアウトし、カウンターの加算も書き換え。

```diff:main.dart
-  int _counter = 0;
+  //int _counter = 0;

   void _incrementCounter() {
+    /*
     setState(() {
       // This call to setState tells the Flutter framework that something has
       // changed in this State, which causes it to rerun the build method below
       // so that the display can reflect the updated values. If we changed
       // _counter without calling setState(), then the build method would not be
       // called again, and so nothing would appear to happen.
       _counter++;
     });
+    */
+    context.read<MyModel>().increment();
   }
```
`context.read<MyModel>()`で該当するクラスのインスタンスを取得できるようです。

更新に応じて表示される`_counter`も書き換えます。

```diff:main.dart
             Text(
-              '$_counter',
+              //'$_counter',
+              '${context.watch<MyModel>().counter}',
               style: Theme.of(context).textTheme.headline4,
             ),
```

更新を監視する時は`context.watch<MyModel>()`となるようです。
`context.select<MyModel>()`というのもあって、同じ監視でも`MyModel`の中味の一部のみを監視できるのだとか。今回は、_counterしかないんですけど。

これで動くはずです。[全体の変更はこちら（GitHub）。](https://github.com/hidea/myApp2Provider/commit/5699bd93eaf869b9e23f43d87c11ae1e54f73afb)

# Widgetに手を入れる

もう少しパターンを試してみます。
`counter`の表示部分を`Widget`に分離してみます。

```dart:main.dart
class Counter extends StatelessWidget {
  const Counter({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      '${context.watch<MyModel>().value}',
      style: Theme.of(context).textTheme.headline4,
    );
  }
}
```
`MyApp`の下位にあたる`Widght`になるので（？）、`context`から`MyModel`を参照できます。
`MyHomePage::build`の表示部分も`Counter`に置き換えました。

```diff:main.dart
-            Text(
-              //'$_counter',
-              '${context.watch<MyModel>().counter}',
-              style: Theme.of(context).textTheme.headline4,
-            ),
+            Counter(),
```

ここで気がついたのは、`StatefulWidget`ではなく`StatelessWidget`でよいということ。
`MyHomePage`も`StatelessWidget`に変更。

```diff:main.dart
-class MyHomePage extends StatefulWidget {
+class MyHomePage extends StatelessWidget {
```

そうすると、State管理する以下の行も全部不要になってしまいます。コメントアウト。

```dart:main.dart
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // int _counter = 0;
```

問題なく動作しました。
コメントアウトを整理した[最終的なコード（GitHub）](https://github.com/hidea/myApp2Provider)です。

# まとまっていないまとめ

いちおう書いた範囲での使い方は理解したつもりになれました。

ただ、`ChangeNotifierProvider`を利用する手法はどうも`scoped_model`（[同名のパッケージ](https://pub.dev/packages/scoped_model)のことか手法自体の呼称かは不明）の代わりに使う書き方のようで`Provider`の本懐ではないようです。
現時点で、`ChangeNotifierProvider`以外にもある`なんたらProvider`がまるで理解の範囲外で、つまり`Provider`の使い方を理解したとは到底言えそうもありません。

書いて動かしてやっとわかったこともあって、
- `ChangeNotifier`は`Provider`ではなく、そもそもFlutterのクラス。
- 今回のサンプルのように更新する値がひとつなら`ValueNotifier`（== class ValueNotifier<T> extends ChangeNorifier）でもよい。

とりあえずデータ（オブジェクト）を分離することができれば、これだけでもよりシンプルに書けるということはわかりました。
あくまで自分理解用の記事になってしまいましたが、どこかの誰かのお役に立てれば幸いです。

さて、長年放置している[新刊.net](http://sinkan.net)アプリをつくらないと。