DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter GoRouter Advanced — Nested Navigation, Auth Guards, and Deep Links

Flutter GoRouter Advanced — Nested Navigation, Auth Guards, and Deep Links

Practical GoRouter patterns beyond the basics.

Nested Navigation with ShellRoute

final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) => ScaffoldWithBottomNav(child: child),
      routes: [
        GoRoute(path: '/home', builder: (c, s) => const HomePage()),
        GoRoute(path: '/search', builder: (c, s) => const SearchPage()),
        GoRoute(path: '/profile', builder: (c, s) => const ProfilePage()),
      ],
    ),
    // Auth screen lives outside ShellRoute
    GoRoute(path: '/login', builder: (c, s) => const LoginPage()),
  ],
);

class ScaffoldWithBottomNav extends StatelessWidget {
  const ScaffoldWithBottomNav({required this.child, super.key});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        onTap: (i) => switch (i) {
          0 => context.go('/home'),
          1 => context.go('/search'),
          _ => context.go('/profile'),
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Authentication Guard (redirect)

final router = GoRouter(
  redirect: (context, state) {
    final isLoggedIn = ref.read(authStateProvider).value != null;
    final isOnLoginPage = state.matchedLocation == '/login';

    if (!isLoggedIn && !isOnLoginPage) return '/login';
    if (isLoggedIn && isOnLoginPage) return '/home';
    return null; // no redirect
  },
  refreshListenable: GoRouterRefreshStream(
    supabase.auth.onAuthStateChange.map((e) => e.session),
  ),
  routes: [...],
);

// Utility that reacts to auth state changes in real time
class GoRouterRefreshStream extends ChangeNotifier {
  GoRouterRefreshStream(Stream stream) {
    _sub = stream.listen((_) => notifyListeners());
  }
  late final StreamSubscription _sub;
  @override
  void dispose() { _sub.cancel(); super.dispose(); }
}
Enter fullscreen mode Exit fullscreen mode

Deep Links: Path Parameters and Query Strings

// URL like /posts/123?highlight=flutter
GoRoute(
  path: '/posts/:postId',
  builder: (context, state) {
    final postId = state.pathParameters['postId']!;
    final highlight = state.uri.queryParameters['highlight'];
    return PostDetailPage(postId: postId, highlight: highlight);
  },
),

// Nested paths
GoRoute(
  path: '/users/:userId',
  builder: (c, s) => UserPage(userId: s.pathParameters['userId']!),
  routes: [
    GoRoute(
      path: 'posts/:postId',  // /users/:userId/posts/:postId
      builder: (c, s) => UserPostPage(
        userId: s.pathParameters['userId']!,
        postId: s.pathParameters['postId']!,
      ),
    ),
  ],
),
Enter fullscreen mode Exit fullscreen mode

Summary

ShellRoute         → shared bottom nav layout (nested navigation)
redirect           → auth guard (refreshListenable reacts to state changes)
pathParameters /
queryParameters    → extract data from the URL
Deep links         → same routing for both web and mobile
Enter fullscreen mode Exit fullscreen mode

Mastering GoRouter gives you unified control from URL design to auth flows.

Top comments (0)