Bottom navigation bar is reloading all the widgets each time I press a navigation item
Asked Answered
B

5

19

The following code is almost a copy-paste from the official flutter GitHub example for the bottom navigation bar. The problem is, at the opening of the widget containing the views, everything is preloaded. And each time I press a bottom bar element I have all of the views reloaded. In another word when I press on the first item, the log shows me that the other ones(second and third) are making network calls. When I press the second, the log shows me that even the first one is making network calls. When I debug I figured out that each time I select a widget from the bottom bar the widget that contains it rebuilds(there is a call to the method build). Is it normal behavior? this is the code of the containing widget

class MainScreen extends StatefulWidget{
    
    @override
      State<StatefulWidget> createState()=> MainScreenState();
    
}


class MainScreenState extends State<MainScreen> with TickerProviderStateMixin{
      GlobalKey<ScaffoldState> scaffoldState = new GlobalKey();
      int _currentSelection = 0;
      BottomNavigationBarType _navigationBarType = BottomNavigationBarType.fixed;
      Li

    st<NavigationIconView> _navigationIcons;


    @override
      void initState() {
       

    super.initState();
        _navigationIcons = <NavigationIconView>[
          new NavigationIconView(
        

    icon: const Icon(Icons.home),
        title: 'Главная',
        vsync: this

      ),
      new NavigationIconView(
        icon: const Icon(Icons.map),
        title: 'Квесты',
        vsync: this
      ),
      new NavigationIconView(
        icon: const Icon(Icons.dehaze),
        title: 'Профиль',
        vsync: this
      )
    ];

    for(NavigationIconView v in _navigationIcons)
      v.controller.addListener(_rebuild);

    _navigationIcons[_currentSelection].controller.value = 1.0;
      }


    @override
      void dispose() {
        for(NavigationIconView v in _navigationIcons)
          v.controller.dispose();
        super.dispose();
    }

     @override
      Widget build(BuildContext context) {
        final BottomNavigationBar botNavBar = new BottomNavigationBar(
          items: _navigationIcons
              .map((NavigationIconView navigationView) => navigationView.item)
              .toList(),
          currentIndex: _currentSelection,
          fixedColor: Colors.green,
          type: _navigationBarType,
          onTap: (int index) {
            setState(() {
              _navigationIcons[_currentSelection].controller.reverse();
              _currentSelection = index;
              _navigationIcons[_currentSelection].controller.forward();
              print('pressed : $_currentSelection');
            });
          },
        );
        return new Scaffold(
          key: scaffoldState,
          body: new  Center(
            key: new Key('Main view container'),
            child: new FutureBuilder<Widget>(
                future: _buildTransitionsStack(),
                builder: (BuildContext context, AsyncSnapshot<Widget> snapshot){
                  if(!snapshot.hasError) return snapshot.data;
                  else{
                    print('sh3t happened in main : ${snapshot.error}');
                  }
                }
            ),
          ),
      bottomNavigationBar: botNavBar,

    );
  }


    Future<Widget> _buildTransitionsStack() {
        final List<FadeTransition> transitions = <FadeTransition>[];
    
        return _showMain().then((mainWidget){
          transitions.add(_navigationIcons[0]
           

    .transition(_navigationBarType,mainWidget, context));
          print('size ${transitions.length}');
        }).then((_){
          transitions.add(_navigationIcons[1].transition(_navigationBarType,
              _showQuest(), context));
    
          transitions.add(_navigationIcons[2].transition(_navigationBarType,
              _showProfile(), context));
    
          transitions.sort((FadeTransition a, FadeTransition b) {
          final Animation<double> aAnimation = a.opacity;
          final Animation<double> bAnimation = b.opacity;
          final double aValue = aAnimation.value;
          final double bValue = bAnimation.value;
          return aValue.compareTo(bValue);
        });
    
          return new Stack(children: transitions);
        });
        }

  Future<Widget> _showMain(){
     return _getToken().then((token){
      return new FeedView(token);
    });
  }

  Widget _showQuest(){
//    return DetailableListScreen(ViewModelType.QUEST);

    return new QuestScreen();

  }

  Widget _showProfile(){
    return new Text('profile');
//    TODO
  }


  void _rebuild() {
    setState(() {

    });
  }

  Future<String> _getToken() async{
    return await SharedPreferences.getInstance()
        .then((SharedPreferences sp)=> sp.getString(TOKEN)
    );
  }
}}
Bwana answered 15/6, 2018 at 23:53 Comment(0)
E
4

I definitively recommend watching @amrnt video. But for those out there who want the straight answer, you need to instantiate a PageStorageKey for each page of your bottom bar sections and then make each page to receive its PageStorageKey via constructor.

Empery answered 19/8, 2018 at 17:1 Comment(1)
someone can refer this video. youtube.com/watch?v=GK8KNoN0AGsEarwax
G
31

The solution to keep the pages alive when switching the tabs is wrapping your Pages in a IndexedStack.

class Tabbar extends StatefulWidget {
  Tabbar({this.screens});

  static const Tag = "Tabbar";
  final List<Widget> screens;
  @override
  State<StatefulWidget> createState() {
  return _TabbarState();
  }
}

class _TabbarState extends State<Tabbar> {
  int _currentIndex = 0;
  Widget currentScreen;

  @override
  Widget build(BuildContext context) {
    var _l10n = PackedLocalizations.of(context);

    return Scaffold(
  body: IndexedStack(
    index: _currentIndex,
    children: widget.screens,
  ),
  bottomNavigationBar: BottomNavigationBar(
    fixedColor: Colors.black,
    type: BottomNavigationBarType.fixed,
    onTap: onTabTapped,
    currentIndex: _currentIndex,
    items: [
      BottomNavigationBarItem(
        icon: new Icon(Icons.format_list_bulleted),
        title: new Text(_l10n.tripsTitle),
      ),
      BottomNavigationBarItem(
        icon: new Icon(Icons.settings),
        title: new Text(_l10n.settingsTitle),
      )
    ],
  ),
);
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }
}
Girvin answered 4/4, 2019 at 9:51 Comment(3)
I tried this and IndexedStack makes the app very slow and even the widgets do not work properly.Solicitor
I faced the same issue, it is very slow with 'IndexedStack'Featherveined
IndexedStack preloads all the widgets, But I want the widget should load once when we select any menu item the first time.Stroup
E
4

I definitively recommend watching @amrnt video. But for those out there who want the straight answer, you need to instantiate a PageStorageKey for each page of your bottom bar sections and then make each page to receive its PageStorageKey via constructor.

Empery answered 19/8, 2018 at 17:1 Comment(1)
someone can refer this video. youtube.com/watch?v=GK8KNoN0AGsEarwax
N
2

Better way is to use IndexedStack instead of PageStorage or AutomaticKeepAliveClientMixin.

class _MainActivityState extends State<MainActivity> {

  int _selectedPage = 0;
  List<Widget> pageList = List<Widget>();

  @override
  void initState() {
    pageList.add(HomePage());
    pageList.add(ChatPage());
    super.initState();
  }

 @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: IndexedStack(
        index: _selectedPage,
        children: pageList,
      ),
      //Bottom Navigation Bar added
      bottomNavigationBar: BottomNavigationBar(
    .....

IndexedStack Widget is sub-class of Stack Widget It shows single child from list of provided Childs. Its size as big as largest child. It keep state of all Childs.

Neuritis answered 22/9, 2020 at 6:17 Comment(0)
S
0

Use PageStorageKey to store data and retrieve it later to save the memory. PageStorageKey is best in case of efficient memory usage for more tabs in bottomNavigationBar. https://api.flutter.dev/flutter/widgets/PageStorageKey-class.html

Semanteme answered 1/12, 2022 at 22:13 Comment(0)
D
0

Use lazy_load_indexed_stack more efficient than IndexedStack all the widgets specified in the children of the IndexedStack will be built.

Dutybound answered 20/7 at 11:27 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Ulland

© 2022 - 2024 — McMap. All rights reserved.