[Flutter] DialogからChangeNotifierの状態にアクセスする

# 今回作成したもの
簡単そうに見えて結構手間取ったのでやり方を記録する。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">DialogにChangeNotifierを渡して進捗率を表示するサンプル <a href="https://t.co/Pb3VfBmkpv">pic.twitter.com/Pb3VfBmkpv</a></p>&mdash; かーにゃ (@popy1017) <a href="https://twitter.com/popy1017/status/1378669418792677378?ref_src=twsrc%5Etfw">April 4, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://github.com/popy1017/flutter_dialog_provider

# 何が面倒か
Dialogは、`showDialog`呼び出し元のWidgetではなくRootに追加されるため、`ChangeNotifierProvider`以下のWidgetで`showDialog`を呼び出したとしても`ChangeNotifier`の状態にはアクセスできない。

```:Dialog表示時のWidgetTreeイメージ
# なんとなくの期待(間違い)
MaterialApp
  ┗━ ChangeNotifierProvider
       ┗━ Home
            ┗━ Dialog

# 実際
MaterialApp
  ┣━ ChangeNotifierProvider
  ┃   ┗━ Home
  ┃
  ┗━ Dialog          
```
>`MaterialApp`を`ChangeNotifierProvider`でラップしてしまうという方法でもできると思います。アプリの設定など、すべての画面で参照する可能性のある状態はそのほうがいいかも。

# どうするか
`ChangeNotifierProvider.value`を使います。

https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html#reusing-an-existing-instance-of-changenotifier

>ドキュメントの和訳
すでにChangeNotifierのインスタンスがあり、それを公開したい場合は、デフォルトのコンストラクターの代わりに`ChangeNotifierProvider.value`を使用する必要があります。

```dart:コード例(home.dart)
  void _showProgressView(BuildContext context) {
    // ChangeNotifer継承クラスを呼び出す
    final DownloadModel model =
        Provider.of<DownloadModel>(context, listen: false);

    showGeneralDialog<void>(
      ~~
      pageBuilder: (BuildContext context, Animation<void> animation,
          Animation<void> secondaryAnimation) {
        return ChangeNotifierProvider.value(
          value: model,  // <= ここでそれを渡す
          child: ProgressDialog(), // <= この中で使えるようになる
        );
      },
    );
  }
```

```dart:progress_dialog.dart
class ProgressDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 状態を取ってくる
    final double progress = context.watch<DownloadModel>().progress;

    return AlertDialog(
      backgroundColor: Colors.transparent,
      elevation: 0,
      content: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,

          // 状態(progress)を使って描画を変える
          children: [
            CircularProgressIndicator(value: progress),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16),
              child: LinearProgressIndicator(value: progress),
            ),
            Text(
              '${(progress * 100).toStringAsFixed(1)}%',
              style: Theme.of(context)
                  .textTheme
                  .headline4
                  .copyWith(color: Colors.white),
            ),
          ],
        ),
      ),
    );
  }
}
```
## アンチパターン
上記のドキュメントにも記載がありますが、以下のような書き方はNG

```dart:NGパターン

// DON'T use ChangeNotifierProvider.value to create your ChangeNotifier.
ChangeNotifierProvider.value(
  value: new MyChangeNotifier(),
  child: ...
)

// DON'T create your ChangeNotifier from variables that can change over the time.
int count;

ChangeNotifierProvider(
  create: (_) => new MyChangeNotifier(count),
  child: ...
)

// DON'T reuse an existing ChangeNotifier using the default constructor
MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

```