1

I'm using Flutter Provider package for managing currentIndex state of CupertionTabBar. I'm doing this instead of using setState of StatefulWidget because I want to programmatically update current active tab from within the tab view pages.

Here's the app code:

import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(
        brightness: Brightness.light,
      ),
      home: ChangeNotifierProvider(
        create: (context) => AppState(),
        child: const TabsPage(),
      ),
    );
  }
}

class TabsPage extends StatelessWidget {
  const TabsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<AppState>(
      builder: (context, appState, child) {
        return CupertinoTabScaffold(
          tabBar: CupertinoTabBar(
            backgroundColor: CupertinoColors.white,
            currentIndex: appState.currentTabIndex,
            onTap: (index) => appState.currentTabIndex = index,
            items: const [
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.house),
                activeIcon: Icon(CupertinoIcons.house_fill),
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.play_rectangle),
                activeIcon: Icon(CupertinoIcons.play_rectangle_fill),
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.heart),
                activeIcon: Icon(CupertinoIcons.heart_fill),
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.music_albums),
                activeIcon: Icon(CupertinoIcons.music_albums_fill),
              ),
              BottomNavigationBarItem(
                icon: Icon(CupertinoIcons.ellipsis_circle),
                activeIcon: Icon(CupertinoIcons.ellipsis_circle_fill),
              ),
            ],
          ),
          tabBuilder: (context, index) {
            return const TabPage();
          },
        );
      },
    );
  }
}

class TabPage extends StatelessWidget {
  const TabPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoTabView(
      builder: (context) => Consumer<AppState>(
        builder: (context, appState, child) => Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Current Tab: ${appState.currentTabIndex}'),
              const SizedBox(height: 8.0),
              ...List.generate(
                5,
                    (index) => Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CupertinoButton.filled(
                    child: Text('Go to tab $index'),
                    onPressed: () => appState.currentTabIndex = index,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class AppState extends ChangeNotifier {
  int _currentTabIndex = 0;

  int get currentTabIndex => _currentTabIndex;

  set currentTabIndex(int index) {
    _currentTabIndex = index;
    notifyListeners();
  }
}

Checkout the live code with results on DartPad.

As you can see although AppState.currentTabIndex is updating and reflected in Text widget but current active BottomNavigationBarItem UI is not updating.

Your help is appreciated.

rmalviya
  • 1,847
  • 12
  • 39

1 Answers1

0

As mentioned in the docs of CupertinoTabScaffold tabBar property:

The CupertinoTabBar.currentIndex is only used to initialize a CupertinoTabController when no controller is provided. Subsequently providing a different CupertinoTabBar.currentIndex does not affect the scaffold or the tab bar's active tab index. To programmatically change the active tab index, use a CupertinoTabController.

If CupertinoTabBar.onTap is provided, it will still be called. CupertinoTabScaffold automatically also listen to the CupertinoTabBar's onTap to change the controller's index and change the actively displayed tab in CupertinoTabScaffold's own main content area.

So, holding instance of CupertinoTabController in AppState and updating index on it works:

import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(
        brightness: Brightness.light,
      ),
      home: ChangeNotifierProvider(
        create: (context) => AppState(),
        child: const TabsPage(),
      ),
    );
  }
}

class TabsPage extends StatelessWidget {
  const TabsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final appState = Provider.of<AppState>(context, listen: false);
    return CupertinoTabScaffold(
      controller: appState.tabController,
      tabBar: CupertinoTabBar(
        backgroundColor: CupertinoColors.white,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.house),
            activeIcon: Icon(CupertinoIcons.house_fill),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.play_rectangle),
            activeIcon: Icon(CupertinoIcons.play_rectangle_fill),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.heart),
            activeIcon: Icon(CupertinoIcons.heart_fill),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.music_albums),
            activeIcon: Icon(CupertinoIcons.music_albums_fill),
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.ellipsis_circle),
            activeIcon: Icon(CupertinoIcons.ellipsis_circle_fill),
          ),
        ],
      ),
      tabBuilder: (context, index) {
        return const TabPage();
      },
    );
  }
}

class TabPage extends StatelessWidget {
  const TabPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoTabView(
      builder: (context) {
        final appState = Provider.of<AppState>(context, listen: false);
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Current Tab: ${appState.tabController.index}'),
              const SizedBox(height: 8.0),
              ...List.generate(
                5,
                (index) => Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CupertinoButton.filled(
                    child: Text('Go to tab $index'),
                    onPressed: () => appState.tabController.index = index,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

class AppState extends ChangeNotifier {
  final tabController = CupertinoTabController();

  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }
}

rmalviya
  • 1,847
  • 12
  • 39