DEV Community

Twilight
Twilight

Posted on

Understanding ShellRoute in go_router: Managing Shared Layouts Effectively

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
        ),
      ],
    ),
  ],
);
Enter fullscreen mode Exit fullscreen mode

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

  1. Consistent Layout:

    • The ShellRoute builder defines shared UI components like Scaffold and BottomNavigationBar.
    • These components persist across navigations because Flutter’s widget diffing mechanism ensures unchanged widgets are reused.
  2. Dynamic Content:

    • The child parameter dynamically updates based on the active route. Only the child changes, keeping the rest of the layout intact.
  3. Flexible Nesting:

    • Sub-routes under ShellRoute inherit the layout, while routes outside the ShellRoute (like /profile) replace it entirely.

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
            ),
          ],
        ),
      ],
    ),
  ],
);
Enter fullscreen mode Exit fullscreen mode

Navigation Examples

  1. Embedded Navigation:
    • Navigate within the ShellRoute and keep the BottomNavigationBar visible:
   context.go('/home/detail');
Enter fullscreen mode Exit fullscreen mode
  1. Full-Screen Navigation:
    • Navigate outside the ShellRoute and overlay the entire screen:
   context.go('/settings/detail');
Enter fullscreen mode Exit fullscreen mode

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 πŸ“Œ

  1. ShellRoute is ideal for managing shared layouts like BottomNavigationBar across routes.
  2. Navigator keys allow fine-grained control over navigation, enabling hybrid behaviors within the same app.
  3. 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)