Flutter CupertinoTabBar: cannot switch to a tab by index on a stream event

2.1k Views Asked by At

Trying to activate CupertinoTabBar's tab 0 while tab 1 is active on a stream event like this:

{

class HomeScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomeScreenState();
}

class HomeScreenState extends State<HomeScreen> {

  int _currentTabIndex = 0;

  @override
  void initState() {
    super.initState();
    _drawerStream.listen((state) {
      if (_currentTabIndex != 0) {
        SchedulerBinding.instance.addPostFrameCallback((_) {
          setState(() => _currentTabIndex = 0);
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: Drawer(
        elevation: 0.0,
        child: DrawerScreen(),
      ),
      body: CupertinoTabScaffold(
        tabBar: CupertinoTabBar(
          onTap: (index) {
            _currentTabIndex = index;
          },
          currentIndex: _currentTabIndex,
          backgroundColor: Colors.white,
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              title: Text('Main'),
              icon: Icon(IconData(0xe800), size: 20),
            ),
            BottomNavigationBarItem(
              title: Text('Goodies'),
              icon: Icon(IconData(0xe84b), size: 20),
            ),
          ],
        ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              switch (index) {
                case 0: return MainScreen();
                case 1: return GoodiesScreen();
              }
            },
          );
        },
      ),
    );
  }
}
}

It nothing happens visually when an event comes from _drawerStream. Still tracing what's going on using the debugger it found that CupertinoTabBar widget builds 2 times and 1st time it has current index parameter 0, what we actually need. But the second run it rebuilds with current index parameter set to 1, which is not what we want.

What the reason for that, how we can switch to a tab on an external event?

2

There are 2 best solutions below

0
On

It's solved using StreamBuilder, a method that was actually attempted first. This method has a different caveat: it switches to tab 0 when we have to stay on, for example, 1st tab and it happens when we come back from another screen open on top of the screen with tabs.

What's wrong this time? The stream builder re-emits last event (BehaviourSubject based stream) and this way _currentTabIndexis set to 0, while we need it to keep a current value. The solution is to "remember" the last event and recognize it just re-emitted. This may not solve all similar issues, but at least gives a clue.

A piece of code to illustrate the solution:

class HomeScreenState extends State<HomeScreen> {

  int _currentTabIndex = 0;
  AsyncSnapshot<UIState> lastSnapshot;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: Drawer(
        elevation: 0.0,
        child: DrawerScreen(),
      ),
      body: StreamBuilder(
        stream: _drawerStream,
        builder: (context, AsyncSnapshot<UIState> snapshot) {
          if (_currentTabIndex != 0 && lastSnapshot != snapshot) {
            SchedulerBinding.instance.addPostFrameCallback((_) =>
              setState(() => _currentTabIndex = nextTab));
          }
          lastSnapshot = snapshot;
          return CupertinoTabScaffold(
            tabBar: CupertinoTabBar(
              onTap: (index) {
                _currentTabIndex = index;
              },
              currentIndex: _currentTabIndex,
              backgroundColor: Colors.white,
              items: <BottomNavigationBarItem>[
                BottomNavigationBarItem(
                  title: Text('Main'),
                  icon: Icon(IconData(0xe800), size: 20),
                ),
                BottomNavigationBarItem(
                  title: Text('Goodies'),
                  icon: Icon(IconData(0xe84b), size: 20),
                ),
              ],
            ),
            tabBuilder: (BuildContext context, int index) {
              return CupertinoTabView(
                builder: (BuildContext context) {
                  switch (index) {
                    case 0: return MainScreen();
                    case 1: return GoodiesScreen();
                  }
                },
              );
            },
          ),
        );
      }
}
0
On

You can also use GlobalKeys and your problem should go away. Here is a clear tutorial on how to do it

https://medium.com/@info_4766/ios-tab-bar-in-flutter-9379cf09df31