Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return to a nested route when switching between tabs with ShellRoute and GoRouter 5.0?

I’m trying to get nested navigation to work with ShellRoute in go_router 5.0.

As the example below shows, I can correctly navigate to the (nested) product page when clicking on a product.

But if I switch to the cart tab and back to products, I'm taken back to the (root) products page rather than the (nested) product page:

ShellRoute example with GoRouter 5.0

This is how I've setup my router:

enum AppRoute {
  products,
  product,
  cart,
  account,
}

final goRouter = GoRouter(
    initialLocation: '/products',
    navigatorKey: _rootNavigatorKey,
    debugLogDiagnostics: true,
    routes: [
      ShellRoute(
        navigatorKey: _shellNavigatorKey,
        builder: (context, state, child) {
          return ScaffoldWithBottomNavBar(child: child);
        },
        routes: [
          // Products
          GoRoute(
            path: '/products',
            name: AppRoute.products.name,
            redirect: (context, state) {
              print(state.toString());
              return null;
            },
            pageBuilder: (context, state) => NoTransitionPage(
              key: state.pageKey,
              restorationId: state.pageKey.value,
              child: const ProductsListScreen(),
            ),
            routes: [
              GoRoute(
                path: ':id',
                name: AppRoute.product.name,
                pageBuilder: (context, state) {
                  final productId = state.params['id']!;
                  // TODO: Cupertino slide transition
                  return MaterialPage(
                    key: state.pageKey,
                    restorationId: state.pageKey.value,
                    child: ProductScreen(productId: productId),
                  );
                },
              ),
            ],
          ),
          // Shopping Cart
          GoRoute(
            path: '/cart',
            name: AppRoute.cart.name,
            pageBuilder: (context, state) => NoTransitionPage(
              key: state.pageKey,
              child: const ShoppingCartScreen(),
            ),
          ),
          // Account page
          GoRoute(
            path: '/account',
            name: AppRoute.account.name,
            pageBuilder: (context, state) => NoTransitionPage(
              key: state.pageKey,
              child: const AccountScreen(),
            ),
          ),
        ],
      ),
    ],
  );

This is the build method of the ScaffoldWithBottomNavBar widget:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: widget.child,
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        unselectedItemColor: Colors.grey,
        selectedItemColor: Colors.black87,
        currentIndex: _selectedIndex,
        items: [
          BottomNavigationBarItem(
            icon: const Icon(
              Icons.list,
            ),
            label: 'Products'.hardcoded,
          ),
          BottomNavigationBarItem(
            icon: const ShoppingCartIcon(),
            label: 'Cart'.hardcoded,
          ),
          BottomNavigationBarItem(
            icon: const Icon(
              Icons.account_circle,
            ),
            label: 'Account'.hardcoded,
          ),
        ],
        onTap: (index) => _tap(context, index),
      ),
    );
  }

In the tab button callback, I do this:

  void _tap(BuildContext context, int index) {
    setState(() => _selectedIndex = index); // used for the highlighted state
    // navigate to the target route based on the tab index
    if (index == 0) {
      context.goNamed(AppRoute.products.name);
    } else if (index == 1) {
      context.goNamed(AppRoute.cart.name);
    } else if (index == 2) {
      context.goNamed(AppRoute.account.name);
    }
  }

I understand why this does not work since I’m telling GoRouter to go to each of the (root) routes inside the ShellRoute.

Ideally, I'd want a way to switch to a specific tab by index, and have GoRouter "remember" if it was on a nested or top-level route.

Note that unlike TabBar, BottomNavigationBar does not have a controller I can use to switch the index.

GoRouter 5.0 is new and there isn't much documentation or examples showing these kind of common use cases.

Anyone knows how to approach this?

Note: one of my ideas was to store the selected product ID as application state somewhere, and use it inside a redirect function inside the /products route, but then it becomes complex to know where I should reset that state to null, and my experiments with NavigationObserver did not yield the desired result.

Update 28th Jun 2023

GoRouter 7.1.0 added a new StatefulShellRoute class to support this.

I've written a full tutorial explaining how to use it:

  • Flutter Bottom Navigation Bar with Stateful Nested Routes using GoRouter
like image 460
bizz84 Avatar asked Dec 18 '25 23:12

bizz84


1 Answers

Sadly this is the missing piece to make it the perfect router. As you noticed this is the issue to track and the team is already on it: https://github.com/flutter/flutter/issues/99124.

like image 70
JPM Avatar answered Dec 21 '25 12:12

JPM



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!