Stateが変更されてもModalBottomSheetが再描画されずハマったので。
状況は↓こんな感じ。
ググってもshowModalBottomSheetの中でStatefulBuilderを使ってsetState()してる記事しか見当たらず、Riverpodの旨味を活かせてない感がありました。
対象読者
- Flutterのソースが読める人
- Riverpodを使ったことがある人
FlutterやRiverpodの使い方は本記事では説明しないので公式Docsやその他記事を参照してください。
結論
- showModalBottomSheetの中でConsumer Widgetを使う
- Consumer Widgetのbuilder内でwatchしてその値を参照する
何故こんな事が起きるのか
※推測多め。
修正前のWidget Treeはこんな感じ。
下部のCenter
配下がModalBottomSheetのツリー。
showModalBottomSheet()
(おそらくDialogも?)を実行するとModalBottomSheetのツリーは MyHomePage
配下ではなく、別のツリーとして構築されてしまう。そのためConsumerで定義したProviderにアクセスできず、変更を受け取れない(多分)。
解決策
Consumer Widgetの使い方
ちょいちょい出ているConsumer Widgetは部分再描画するためのウィジェット。
Consumer can be used to listen to providers inside a StatefulWidget or to rebuild as few widgets as possible when a provider updates.
builder
プロパティに監視したいProviderと変更時に再描画したいWidgetを書いてあげる。
watchしているState変数を再描画したいWidget内で参照しないと再描画されないので注意!
Consumer(
builder: (context, ref, child) {
final xxxState = ref.watch(監視したいProvider);
return 再描画したいWidget;
},
);
Consumer WidgetはあくまでWidgetなので、これをshowModalBottomSheetの中に書けばOK。(謎にここで数時間ハマった。。)
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
+ return Consumer(
+ builder: (context, ref, child) {},
+ );
},
);
修正後のWidget Treeはこんな感じ。
ModalBottomSheetのツリー内にConsumerがあるので変更を受け取って再描画できる。
余談
StatelessWidgetはConsumerWidgetに、StatefulWidgetはConsumerStatefulWidgetに書き換えるのが基本だが、Stateが変化するとページ全体が再描画されてしまう&再描画のコストが掛かってしまう。
個人的には全部StatelessWidgetで書いて、Stateの状態を受け取りたいところだけCosumerで囲む方が再描画のコストも低くなるし、StateはRiverpod、UIはStatelessWidgetに分離出来るから設計的に気持ち良く感じる。
サンプルソース
Commit履歴に失敗パターンと成功パターンを打っているので気になる方は是非。
参考情報
最後に
筆者はFlutterもRiverpodも初心者で見様見真似で書いてるので間違いがあったらご指摘お願いします