How to Dynamically Create BottomNavigationBar Items from StatefulShellRoute Branches in go_router?

150 views Asked by At

I'm working with the go_router package in Flutter and facing a challenge with the StatefulShellRoute. Specifically, I'm trying to dynamically generate BottomNavigationBar items from the branches of the StatefulShellRoute, as suggested in the go_router documentation. The example provided by the package hardcodes these items, but comments indicate they should be generated from the route branches.

I'm looking for guidance or an example on how to achieve this. How can I access the necessary data from the branches to create these navigation bar items dynamically? Any code examples or step-by-step instructions would be immensely helpful.

Here's the snippet from the go_router example where the items are hardcoded:

class ScaffoldWithNavBar extends StatelessWidget {
  /// Constructs an [ScaffoldWithNavBar].
  const ScaffoldWithNavBar({
    required this.navigationShell,
    Key? key,
  }) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));

  /// The navigation shell and container for the branch Navigators.
  final StatefulNavigationShell navigationShell;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: navigationShell,
      bottomNavigationBar: BottomNavigationBar(
        // Here, the items of BottomNavigationBar are hard coded. In a real
        // world scenario, the items would most likely be generated from the
        // branches of the shell route, which can be fetched using
        // `navigationShell.route.branches`.
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
          BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
          BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Section C'),
        ],
        currentIndex: navigationShell.currentIndex,
        onTap: (int index) => _onTap(context, index),
      ),
    );
  }

}

Here is the link for the complete runnable example: https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart

Thanks in advance for any assistance you can provide!

1

There are 1 answers

1
Cam Hoang On

I'm new to Flutter as well, and unlike React Native, it's quite challenging to find detailed guidance for certain tasks. I guess that when you mention 'creating these navigation bar items dynamically,' you're expecting to define routes in a single location rather than hardcoding them in multiple places. If that's the case, here is my proposed solution:

First, I declare all the screens that I may have:

final _allScreens = [
  (
    path: '/home',
    title: 'Home',
    navigatorKey: GlobalKey<NavigatorState>(debugLabel: 'home'),
    screen: const HomeScreen(),
    iconData: MdiIcons.home,
    stack: []
  ),
  (
    path: '/account',
    title: 'Account',
    navigatorKey: GlobalKey<NavigatorState>(debugLabel: 'another'),
    screen: const AccountScreen(),
    iconData: MdiIcons.account,
    stack: []
  ),
...
];

The stack is for nested screens of the tab item if any.

  1. Then I define the routes:
final routes = GoRouter(navigatorKey: _rootNavigationKey,
 initialLocation: '/home',
 routes: [
  StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell) => ScaffoldWithBottomTabBar(tabItems: [
            for (var screen in _allScreens)
              (title: screen.title, iconData: screen.iconData)
          ], navigationShell: navigationShell, state: state),
      branches: [
        for (var screen in _allScreens)
          StatefulShellBranch(navigatorKey: screen.navigatorKey, routes: [
            GoRoute(
                path: screen.path,
                pageBuilder: (context, state) =>
                    NoTransitionPage(child: screen.screen),
                routes: [
                  for (var child in screen.stack)
                    GoRoute(
                        path: child.path,
                        pageBuilder: (context, state) =>
                            NoTransitionPage(child: child.screen),
                    )
                ])
           ]) 
       ])
]);
  1. The ScaffoldWithBottomTabBar is much similar to what you currently have.