Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter bottomNavigator with indexed stack

Question regarding navigating between tabs using indexed stack to display relevant page. I'm doing this in order to keep scroll/state of pages. This works fine. I can change the current page displayed by clicking tab - and can also navigate inside each page (each page is wrapped with it's own Navigator). This is the code for rendering the pages.

Widget build(BuildContext context) {
return IndexedStack(
    index: widget.selectedIndex,
    children: List.generate(widget._size, (index) {
  return _buildNavigator(index);
}));

}

Mu problem is that IndexedStack builds all pages at once. In some of my pages I want to load data from an API, I want to do it when the widget first time built and only if the page is currently visible. Is there a way to do so? in my current implementation all widgets build at once and so all my API calls are called even for the pages that are not currently painted.

Not sure if i'm missing something here, or there is a better way to implement bottom navigation bar. BTW i'm also using Provider for state management.

like image 653
tsahnar Avatar asked Jun 10 '20 22:06

tsahnar


3 Answers

@tsahnar yea i have also faced same issue related with api call indexed widget render all widgets provided it to its children at once so when individual pages are independently fetching data from api then here comes the problem

try this :

  • create list of widgets which navigates through your navbar (each widget with key constructor where define PageStorageKey(<key>) for each widgets)
var widgetList = <Widget>[
Page01(key:PageStorageKey(<key>)),
Page02(key:PageStorageKey(<key>))
];
  • then create PageStorageBucket() which stores your widgets state and provides it in future whenever we need it in a lifetime of app even the widget gets disposed from the tree

final _bucket = PageStorageBucket();

  • then var currentIndex = 0;

  • then in your main base page where the bottom navbar exists in your body instead of IndexedStack use body:PageStorage(bucket: _bucket,child:widgetsList[currentIndex])

and create bottomnavbar in that main base page and then onNavbar icon tab manage index page impherial state by setState((){}) the current state to the currentIndex

it should fix your problem tho its too late after a year

like image 107
Himmat rai Avatar answered Nov 12 '22 22:11

Himmat rai


I encountered the same problem. My solution was to save a list of the loaded tabs and then use that to build the list of IndexedStack children inside the Widget build(BuildContext context) method. Then in the onTap method of the BottomNavigationBar, I called setState() to update the list of loaded tabs as well as the current index variable. See below:

class Index extends StatefulWidget {
  const Index({Key? key}) : super(key: key);

  @override
  _IndexState createState() => _IndexState();
}

class _IndexState extends State<Index> {
  int _currentIndex = 0;
  List loadedPages = [0,];

  @override
  Widget build(BuildContext context) {
    var screens = [
      const FirstTab(),
      loadedPages.contains(1) ? const SecondTab() : Container(),
      loadedPages.contains(2) ? const ThirdTab() : Container(),
      loadedPages.contains(3) ? const FourthTab() : Container(),
    ];
    return Scaffold(
      appBar: AppBar(
        // AppBar
      ),
      body: IndexedStack(
        index: _currentIndex,
        children: screens,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          var pages = loadedPages;
          if (!pages.contains(index)) {
            pages.add(index);
          }
          setState(() {
            _currentIndex = index;
            loadedPages = pages;
          });
        },
        items: const [
          // items
        ],
      ),
    );
  }
}

Now, the API calls on the second, third, and fourth tabs don't call until navigated to.

like image 3
nsc211 Avatar answered Nov 12 '22 21:11

nsc211


do you found a solution?
I found the same problem as you and I tried this workaround (i didn't found any issues with it yet)
The idea is to make a new widget to control the visibility state of the widgets that made the api call and build it when it became visible.
In your IndexedStack wrap your _buildNavigator with a widget like this:

class BaseTabPage extends StatefulWidget {

  final bool isVisible;
  final Widget child;

  BaseTabPage({Key key, this.child, this.isVisible});

  @override
  State<StatefulWidget> createState() => _BaseTabPageState();

}

/*
  This state is to prevent tab pages creation before show them. It'll only add the
  child widget to the widget tree when isVisible is true at least one time
  i.e. if the child widget makes an api call, it'll only do when isVisible is true
  for the first time
 */
class _BaseTabPageState extends State<BaseTabPage> {

  bool alreadyShowed = false;

  @override
  Widget build(BuildContext context) {
    alreadyShowed = widget.isVisible ? true : alreadyShowed;

    return alreadyShowed ? widget.child : Container();
  }
}

For example in my code i have something like this to build the navigators for each tab, where _selectedIndex is the selected position of the BottomNavigationBar and tabPosition is the position of that page in the BottomNavigationBar

Widget _buildTabPage(int tabPosition) {
    final visibility = _selectedIndex == tabPosition;
    return BaseTabPage(
      isVisible: visibility,
      child: _buildNavigator(tabPosition),
    );
  }

With this i have the logic of the api call entirely in the children widgets and the bottom navigation knows nothing about them. Let me know if you see something wrong with it since i'm kind of new with flutter.

like image 1
Federico Arrua Avatar answered Nov 12 '22 20:11

Federico Arrua