[Flutter/dart] 親WidgetからのリビルドかsetState()によるリビルドかを判別する
概要
StatefulWidgetでページを作っている際、親Widgetからリビルドされたのか、setState()でページ内からリビルドされたのかを判別したいという状況に遭遇しました。
より具体的には、Providerの子Widgetにページを置いて、ある状態が変更されたらそれがページにも反映されるようにしています。
Providerによるリビルドの場合は、状態が変更したということなので、それを反映させるための処理を行いたいです。
一方、setState()による変更は監視している状態が変更したわけではない(例:BottomNavigationのページの切り替わり)ので、この処理は行いたくありません。
(initState()でやれば良いのでは?と思うかもしれませんが、ProviderなどによるリビルドではinitStateは最初の1回だけしか通りません)
このような状況への対応案を記したいと思います。
方法
StatefulWidget内に親Widgetからのリビルドかどうかを示すbool変数を持たせる
親Widget(Providerなど)からこのStatefulWidgetのコンストラクタを呼ぶときは、このbool変数をtrueにセットする。
buildメソッド内でこの変数の値を参照して所望の処理を行う。その後この変数はfalseにする。
このようにすればビルド時に1度だけ処理を行えば、それ以降のsetState()によるリビルドではこの処理は通りません。
具体例を以下に示します。
このページはBottomNavigationで3つのページを切り替えられるようになっています。
ViewModelはある状態を表すクラスです。ViewModelのインスタンスを元に3つのページを作成します。
このページ作成処理はmodelの内容が変わった場合は行いたいですが、buld()の中にそのまま記述してしまうと、ページが切り替わる度に作成処理が走ってしまい、非効率です。
そこで、上記の対策を行っています。
class basePage extends StatefulWidget{
ViewModel model;
bool buildFromParent;
basePage({this.model, this.buildFromParent});
@override
State<StatefulWidget> createState() {
return basePageState();
}
}
class basePageState extends State<basePage>{
List<Widget>_pageList;
int _pageInd=0;
@override
Widget build(BuildContext context) {
if (widget.buildFromParent) {
_pageList = [APage(model: widget.model,),
BPage(model: widget.model), CPage(model: widget.model,)];
widget.buildFromParent=false; //ここ
}
return Scaffold(
body: _pageList[_pageInd],
bottomNavigationBar:BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today)
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
)
],
currentIndex: _pageInd,
onTap: (index) {
setState(() {
_pageInd = index;
});
},
)
);
}
}
このbasePageを呼び出すコードは以下の通りです。buildFromParentはtrueにします。
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: StoreConnector<AppState, ViewModel>(
distinct: true,
converter: (store)=>ViewModel.create(store),
builder: (BuildContext context, ViewModel model)=>
basePage(model: model, buildFromParent: true,) //ここ
)
);
}
自分はreduxで状態管理をしているのでStoreProviderをよく使います。
最後に
本当はconstなどを使ってなるべくリビルドされないようにするというのが最初の対策なのですが、状態に応じてページの表示内容を変えたい場合もあると思います。
また、Providerを使うならばStatelessWidgetで充分という意見もありますが、BottomNavigationやDropdownButtonのようにStatefulWidgetを使わないといけない場合もあります。
そんな時は是非この方法を試してみてください。
最新記事
すべて表示やりたいこと TextFieldで入力フォームを作りたい。 例えば入力内容が金額の場合、3桁区切りで頭に¥を付けた表記にしたい。 ただしユーザにこれらを入力させるのではなく、ユーザはあくまで数字を入力するだけで、アプリ側で自動でフォーマットしたい。 方法...
現象 やってること iosシミュレータで画像をデバイスのローカルに保存 保存したパスをデータベースに保存 アプリ立ち上げ時にデータベースから画像パスを取得し、そのパスの画像を画面上に表示 起きている現象 iosシミュレータを再起動した場合、上記3で「ファイルパスが見つからな...
やりたいこと 初期値さえ決まればあとは不変な変数がある ただし、コンストラクタ起動時にはまだ決定できない このような変数について late finalで変数を定義 (何らかのタイミングで)初期化されたかどうかをチェックし、されていなければ値を入れる(チェックしないとfina...
Comments