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
ShellRoute
builder defines shared UI components likeScaffold
andBottomNavigationBar
. - These components persist across navigations because Flutterβs widget diffing mechanism ensures unchanged widgets are reused.
- The
-
Dynamic Content:
- The
child
parameter dynamically updates based on the active route. Only thechild
changes, keeping the rest of the layout intact.
- The
-
Flexible Nesting:
- Sub-routes under
ShellRoute
inherit 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
ShellRoute
layout. -
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
ShellRoute
and keep theBottomNavigationBar
visible:
- Navigate within the
context.go('/home/detail');
-
Full-Screen Navigation:
- Navigate outside the
ShellRoute
and 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 π
-
ShellRoute
is ideal for managing shared layouts likeBottomNavigationBar
across 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)