The ShellRoute in the go_router package is a powerful tool for Flutter developers to manage shared layouts like BottomNavigationBar or AppBar across different routes. This article explains how ShellRoute works and demonstrates advanced usage, including handling embedded and full-screen navigation patterns.
What is ShellRoute? 🛤️
ShellRoute acts as a container for child routes, providing a consistent layout that persists across navigations. For example, it ensures that a Scaffold and its BottomNavigationBar remain intact while only the content of the body updates based on the active route.
Example 🧑💻:
ShellRoute(
builder: (context, state, child) => Scaffold(
body: child, // Dynamic content based on the active route
bottomNavigationBar: BottomNavigationBar(
currentIndex: _calculateIndex(state.location),
onTap: (index) {
if (index == 0) context.go('/home');
if (index == 1) context.go('/settings');
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
),
routes: [
GoRoute(
path: '/home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'detail',
builder: (context, state) => const HomeDetailScreen(),
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: 'detail',
builder: (context, state) => const SettingsDetailScreen(),
parentNavigatorKey: _rootNavigatorKey, // Full-screen detail
),
],
),
],
);
In this example, navigating between /home and /settings updates the body while keeping the BottomNavigationBar visible. Nested routes like /home/detail allow finer control over navigation behavior.
How ShellRoute Maintains Layout 🏗️
Route Matching and Widget Diffing
-
Consistent Layout:
- The
ShellRoutebuilder defines shared UI components likeScaffoldandBottomNavigationBar. - These components persist across navigations because Flutter’s widget diffing mechanism ensures unchanged widgets are reused.
- The
-
Dynamic Content:
- The
childparameter dynamically updates based on the active route. Only thechildchanges, keeping the rest of the layout intact.
- The
-
Flexible Nesting:
- Sub-routes under
ShellRouteinherit the layout, while routes outside theShellRoute(like/profile) replace it entirely.
- Sub-routes under
Combining Embedded and Full-Screen Navigation 🔀
Sometimes, you may want a route to behave differently depending on the context:
-
Embedded Navigation: The detail screen appears within the
ShellRoutelayout. -
Full-Screen Navigation: The detail screen replaces the entire layout, including the
BottomNavigationBar.
Solution: Using Navigator Keys
By assigning parentNavigatorKey to specific routes, you can control whether they are displayed within the ShellRoute or as full-screen overlays.
Updated Router Configuration:
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> _shellNavigatorKey = GlobalKey<NavigatorState>();
final GoRouter router = GoRouter(
navigatorKey: _rootNavigatorKey, // Root Navigator for full-screen routes
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey, // Shell Navigator for embedded routes
builder: (context, state, child) => Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _calculateIndex(state.location),
onTap: (index) {
if (index == 0) context.go('/home');
if (index == 1) context.go('/settings');
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
),
routes: [
GoRoute(
path: '/home',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'detail',
builder: (context, state) => const HomeDetailScreen(),
),
],
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: 'detail',
builder: (context, state) => const SettingsDetailScreen(),
parentNavigatorKey: _rootNavigatorKey, // Full-screen detail
),
],
),
],
),
],
);
Navigation Examples
-
Embedded Navigation:
- Navigate within the
ShellRouteand keep theBottomNavigationBarvisible:
- Navigate within the
context.go('/home/detail');
-
Full-Screen Navigation:
- Navigate outside the
ShellRouteand overlay the entire screen:
- Navigate outside the
context.go('/settings/detail');
Why Use Navigator Keys? 🗝️
- Flexibility: Decide at runtime whether a screen should be embedded or full-screen.
- Maintainability: Keep logical route hierarchies while supporting diverse navigation behaviors.
- Performance: Flutter’s widget diffing ensures efficient updates to shared layouts.
Key Takeaways 📌
-
ShellRouteis ideal for managing shared layouts likeBottomNavigationBaracross routes. - Navigator keys allow fine-grained control over navigation, enabling hybrid behaviors within the same app.
- Combining embedded and full-screen navigation patterns enhances user experience while keeping code maintainable.
By understanding and leveraging these techniques, you can create flexible, performant, and user-friendly navigation in your Flutter apps.
Top comments (0)