Bottom navigation with nested navigators
Keep each tab's navigation stack alive with an IndexedStack, one Navigator per tab and correct back-button handling.
FlutterNavigationflutternavigationmaterial3navigator
Code
How it works
What it solves
A bottom bar that simply swaps the body loses where the user was: open a product, switch to the cart tab, come back, and the product page is gone. Apps with several browsing flows need each tab to keep its own history, and the Android back button to respect it.
How it works
Every tab owns a Navigator with its own GlobalKey. An IndexedStack keeps all of them mounted and only shows the selected one, so scroll positions and pushed routes survive tab switches. Tapping the already-active destination pops that tab back to its first route, matching what users expect from native apps.
Back handling uses PopScope. While the active tab can still pop, the system pop is blocked and we pop the inner navigator instead. At a tab root on a non-home tab, back jumps to the first tab, and only from the first tab at its root does the app actually close.
Adapting it
Replace TabSpec.root with your real screens. For named routes inside a tab, pass onGenerateRoute from a per-tab router instead of the single MaterialPageRoute. To open a full-screen page above the bar (a camera, a checkout), push it with Navigator.of(context, rootNavigator: true).
If a tab should rebuild lazily, build its Navigator only after the first visit by tracking a set of visited indexes.
Pitfalls
canPopis read inbuild, so it only refreshes when the shell rebuilds. Add aNavigatorObserverthat callssetStateon push and pop if the system back gesture feels one step late.- All tabs stay in memory; heavy tabs (maps, video) may need lazy creation.
- Do not reuse the same
GlobalKeyacross two shells.
How to use it
Copy the file into your project, then show the shell as your home screen. No extra packages are needed.
home: TabbedShell(tabs: [
TabSpec('Home', Icons.home_outlined, (_) => const HomePage()),
TabSpec('Cart', Icons.shopping_cart_outlined, (_) => const CartPage()),
]),
Use Navigator.of(context).push(...) inside a tab to stay within that tab's stack.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.