今回作成したもの
簡単そうに見えて結構手間取ったのでやり方を記録する。
DialogにChangeNotifierを渡して進捗率を表示するサンプル pic.twitter.com/Pb3VfBmkpv
— かーにゃ (@popy1017) April 4, 2021
何が面倒か
Dialogは、showDialog
呼び出し元のWidgetではなくRootに追加されるため、ChangeNotifierProvider
以下のWidgetでshowDialog
を呼び出したとしてもChangeNotifier
の状態にはアクセスできない。
Dialog表示時のWidgetTreeイメージ
# なんとなくの期待(間違い)
MaterialApp
┗━ ChangeNotifierProvider
┗━ Home
┗━ Dialog
# 実際
MaterialApp
┣━ ChangeNotifierProvider
┃ ┗━ Home
┃
┗━ Dialog
MaterialApp
をChangeNotifierProvider
でラップしてしまうという方法でもできると思います。アプリの設定など、すべての画面で参照する可能性のある状態はそのほうがいいかも。
どうするか
ChangeNotifierProvider.value
を使います。
ドキュメントの和訳
すでにChangeNotifierのインスタンスがあり、それを公開したい場合は、デフォルトのコンストラクターの代わりにChangeNotifierProvider.value
を使用する必要があります。
コード例(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(), // <= この中で使えるようになる
);
},
);
}
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
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: ...
)