17

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.

tsahnar
  • 254
  • 2
  • 12

5 Answers5

5

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
nsc211
  • 51
  • 1
  • 2
3

@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

Himmat rai
  • 57
  • 1
  • 6
3

Use a PageView instead of an IndexedStack

 PageView(
            physics: const NeverScrollableScrollPhysics(),
            controller: _pageController,
            children: const [
              Page1(),
              Page2(),
              Page3(),
            ],
          ),

you can switch pages using the pageController

_pageController.jumpToPage(0);
Johny B
  • 944
  • 8
  • 15
1

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.

0

Flutter will build all widgets inside a stack so if your pages do an API call inside initState, then it will be triggered on build.

What you can do is have a separate function for the API call. Then call the function from the navigator or from the state management.

I hope this helps give you an idea on how to implement this.

digitaljoni
  • 1,357
  • 1
  • 9
  • 12
  • 1
    Thanks, but not sure I follow. I have a separate function for the api call but when do I trigger the call? The widgets in the stack are all built together when the main screen that builds the stack is built. I need to make the call only when user clicks the tab and the page is shown on screen – tsahnar Jun 11 '20 at 05:54