top of page

[Flutter/dart] 親WidgetからのリビルドかsetState()によるリビルドかを判別する


概要


StatefulWidgetでページを作っている際、親Widgetからリビルドされたのか、setState()でページ内からリビルドされたのかを判別したいという状況に遭遇しました。


より具体的には、Providerの子Widgetにページを置いて、ある状態が変更されたらそれがページにも反映されるようにしています。

Providerによるリビルドの場合は、状態が変更したということなので、それを反映させるための処理を行いたいです。

一方、setState()による変更は監視している状態が変更したわけではない(例:BottomNavigationのページの切り替わり)ので、この処理は行いたくありません。


(initState()でやれば良いのでは?と思うかもしれませんが、ProviderなどによるリビルドではinitStateは最初の1回だけしか通りません)


このような状況への対応案を記したいと思います。



方法


  1. StatefulWidget内に親Widgetからのリビルドかどうかを示すbool変数を持たせる

  2. 親Widget(Providerなど)からこのStatefulWidgetのコンストラクタを呼ぶときは、このbool変数をtrueにセットする。

  3. 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を使わないといけない場合もあります。

そんな時は是非この方法を試してみてください。

最新記事

すべて表示

【Flutter/Dart】TextFieldで文字列をフォーマットする

やりたいこと TextFieldで入力フォームを作りたい。 例えば入力内容が金額の場合、3桁区切りで頭に¥を付けた表記にしたい。 ただしユーザにこれらを入力させるのではなく、ユーザはあくまで数字を入力するだけで、アプリ側で自動でフォーマットしたい。 方法 TextInputFormatterを継承し、所望のフォーマット処理を追加することで実現可能。 例えばこの記事などを参照。 以下は個人的に躓いた

【Flutter/Dart】iosシミュレータ起動後にデバイスに保存した画像が見つからない

現象 やってること iosシミュレータで画像をデバイスのローカルに保存 保存したパスをデータベースに保存 アプリ立ち上げ時にデータベースから画像パスを取得し、そのパスの画像を画面上に表示 起きている現象 iosシミュレータを再起動した場合、上記3で「ファイルパスが見つからない」というエラーが出る 原因 保存時のディレクトリを getApplicationDocumentsDirectory() に

【Flutter/dart】late変数が初期化されたかのチェック

やりたいこと 初期値さえ決まればあとは不変な変数がある ただし、コンストラクタ起動時にはまだ決定できない このような変数について late finalで変数を定義 (何らかのタイミングで)初期化されたかどうかをチェックし、されていなければ値を入れる(チェックしないとfinalに値を代入したエラーになるので) この場合、「初期化されたかどうか」はどのようにチェックしたらいいのか分からなかった nul

Comments


あなたの買い物をサポートする
アプリ Shop Plan

iphone6.5p2.png

​いつ何を買うかの計画を立てられるアプリです。

google-play-badge.png
Download_on_the_App_Store_Badge_JP_RGB_blk_100317.png

靴を大切にしよう!靴管理アプリ SHOES_KEEP

納品:iPhone6.5①.png

靴の履いた回数、お手入れ回数を管理するアプリです。

google-play-badge.png
Download_on_the_App_Store_Badge_JP_RGB_blk_100317.png

「後で読む」を忘れないアプリ ArticleReminder

気になった​Webサイトを登録し、指定時刻にリマインダを送れるアプリです

google-play-badge.png
Download_on_the_App_Store_Badge_JP_RGB_blk_100317.png
bottom of page