TheLetch
TheLetch

Reputation: 405

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

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)
    );
  }
}}

Upvotes: 19

Views: 14128

Answers (5)

Muhammad Rashad
Muhammad Rashad

Reputation: 1

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

Upvotes: 0

Mohammad Bilal Akbar
Mohammad Bilal Akbar

Reputation: 170

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

Upvotes: 0

Mohd Danish Khan
Mohd Danish Khan

Reputation: 1101

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.

Upvotes: 2

basedgod
basedgod

Reputation: 2647

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;
    });
  }
}

Upvotes: 31

marp
marp

Reputation: 474

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.

Upvotes: 4

Related Questions