1
votes

I have a problem with the structure of Flutter project.

At the moment structure looks like this:

Homepage with bottomNavigationBar with multiple tabs, each tab is StatefulWidget and contains some heavy processing (remote API calls and data display).

In case I call Navigator.pushNamed from inside of any tab, following happens:

  1. All tabs are being rebuild in the background (making API calls, etc).
  2. New page opens normally.
  3. When I press back button, page closes and all tabs are rebuild again.

So in total everything (each tab) is rebuilt 2 times just to open external navigator page.

Is this some sort of bug? Completely not understandable why it's fully rebuilding bottomNavigationBar just before pushing new route.

How it should work:

  1. When I call Navigator.pushNamed from inside the tab, new page should be open and all bottomNavigationBar tabs should not be rebuild and stay in unchanged state.
  2. When I press back, page should close and user return to the same state of bottomNavigationBar and it's tabs, no rebuilding at all.

Is this possible to achieve?

Here is the code:

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {


  int index = 0;

  final _tab1 = new tab1();   //StatefulWidget, api calls, heavy data processing
  final _tab2 = new tab2();   //StatefulWidget, api calls, heavy data processing


  @override
  Widget build(BuildContext context) {

    debugPrint('homepage loaded:'+index.toString());


    return new Scaffold(
      body: new Stack(
        children: <Widget>[

          new Offstage(
            offstage: index != 0,
            child: new TickerMode(
              enabled: index == 0,
              child: _tab1,
            ),
          ),
          new Offstage(
            offstage: index != 1,
            child: new TickerMode(
              enabled: index == 1,
              child: _tab2,   
            ),
          ),


        ],
      ),
      bottomNavigationBar: new BottomNavigationBar(
        currentIndex: index,
        type: BottomNavigationBarType.fixed,
        onTap: (int index) { setState((){ this.index = index; }); },
        items: <BottomNavigationBarItem>[
          new BottomNavigationBarItem(
            icon: new Icon(Icons.live_help),
            title: new Text("Tab1"),
          ),
          new BottomNavigationBarItem(
            icon: new Icon(Icons.favorite_border),
            title: new Text("Tab 2"),
          ),


        ],
      ),
    );
  }
}

Here is single tab code:

class tab1 extends StatefulWidget {



  @override
  tab1State createState() => new tab1State();
}


class tab1State extends State<tab1> {


  @override
  Widget build(BuildContext cntx) {


    debugPrint('tab loaded');  //this gets called when Navigator.pushNamed called and when back button pressed

    //some heave processing with http.get here...   
    //...


    return new Center(
        child: new RaisedButton(
          onPressed: () {

            Navigator.pushNamed(context, '/some_other_page');


          },
          child: new Text('Open new page'),
        ));


  }

}
2

2 Answers

2
votes

In build you should simply be building the Widget tree, not doing any heavy processing. Your tab1 is a StatefulWidget, so its state should be holding onto the current state (including results of your heavy processing). Its build should simply be rendering the current version of that state.

In tab1state, override initState to set initial values, and possibly start some async functions to start doing the fetching - calling setState once the results are available. In build, render whatever the current state is, bearing in mind that it may only be partially available as the heavy work continues in the background. So, for example, test for values being null and maybe replace them with progress indicators or empty Containers.

You can get more sophisticated by using StreamBuilder and FutureBuilder which make the fetch/setState/(possibly partial)render more elegant.

2
votes

Since you are building BottomNavigationBar inside build function, it will be rebuilt every time state changes.

To avoid this you can build BottomNavigationBar inside initState() method as given below,

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {
  BottomNavigationBar _bottomNavigationBar;

  @override
  void initState() {
    super.initState();

    _bottomNavigationBar = _buildBottomNavigationBar();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: new Center(child: new Text('Hello', style: new TextStyle(decoration: TextDecoration.underline),),),
      bottomNavigationBar: _bottomNavigationBar, // Use already created BottomNavigationBar rather than creating a new one
    );
  }

  // Create BottomNavigationBar
  BottomNavigationBar _buildBottomNavigationBar() {
    return new BottomNavigationBar(
      items: [
        new BottomNavigationBarItem(
            icon: new Icon(Icons.add),
            title: new Text("trends")
        ),
        new BottomNavigationBarItem(
            icon: new Icon(Icons.location_on),
            title: new Text("feed")
        ),
        new BottomNavigationBarItem(
            icon: new Icon(Icons.people),
            title: new Text("community")
        )
      ],
      onTap: (index) {},
    );
  }
}