Back to Rules
Flutter87% popularity

Flutter Navigation with GoRouter

Implement declarative routing with GoRouter for deep linking and nested navigation.

Chris CoxUpdated Feb 22, 2024

Overview

GoRouter has become the standard for Flutter navigation, providing declarative routing that integrates with Flutter's widget system and supports deep linking, redirect logic, and nested navigation out of the box. Its API design embraces the widget tree model rather than fighting against it. Route configuration with GoRouter uses path-based URLs that map directly to widgets. Parameters are extracted via path patterns like /users/:userId and automatically parsed and typed. Query parameters are accessible through the queryParameters map. This approach makes Flutter apps behave like web applications with proper back button support and shareable URLs. Redirect logic handles authentication and navigation guards elegantly. The redirect callback receives the BuildContext and GoRouterState, enabling async checks like token validation or role verification. Returning null continues navigation; returning a path redirects elsewhere. This replaces manual navigation checks scattered throughout the app. Nested navigation with ShellRoute enables persistent UI elements like navigation bars while content changes. Routes can have child routes that render within parent routes, enabling complex app layouts where some elements persist while others change. The Navigator API underneath GoRouter enables custom page transitions and animation overrides. Deep linking configuration on each platform (Android intent filters, iOS URL schemes, web Links) must be set up separately from GoRouter itself. GoRouter handles the routing once URLs reach the app, but platform configurations determine which URLs reach the app in the first place. Proper configuration is essential for app links and universal links to work correctly.

Code Example

.flutterrules
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
import '../screens/dashboard_screen.dart';
import '../screens/project_detail_screen.dart';
import '../screens/settings_screen.dart';
import '../widgets/app_shell.dart';
import '../providers/auth_provider.dart';

final routerProvider = Provider<GoRouter>((ref) {
  final authState = ref.watch(authStateProvider);

  return GoRouter(
    initialLocation: '/',
    debugLogDiagnostics: true,
    redirect: (context, state) {
      final isLoggedIn = authState.valueOrNull != null;
      final isLoggingIn = state.matchedLocation == '/login';

      if (!isLoggedIn && !isLoggingIn) {
        return '/login?redirect=' + state.matchedLocation;
      }

      if (isLoggedIn && isLoggingIn) {
        return '/dashboard';
      }

      return null;
    },
    routes: [
      GoRoute(
        path: '/',
        name: 'home',
        builder: (context, state) => const HomeScreen(),
      ),
      GoRoute(
        path: '/login',
        name: 'login',
        builder: (context, state) {
          final redirect = state.uri.queryParameters['redirect'];
          return LoginScreen(redirectPath: redirect);
        },
      ),
      ShellRoute(
        builder: (context, state, child) => AppShell(child: child),
        routes: [
          GoRoute(
            path: '/dashboard',
            name: 'dashboard',
            builder: (context, state) => const DashboardScreen(),
            routes: [
              GoRoute(
                path: 'project/:projectId',
                name: 'project-detail',
                builder: (context, state) {
                  final projectId = state.pathParameters['projectId']!;
                  return ProjectDetailScreen(projectId: projectId);
                },
                routes: [
                  GoRoute(
                    path: 'settings',
                    name: 'project-settings',
                    builder: (context, state) {
                      final projectId = state.pathParameters['projectId']!;
                      return SettingsScreen(projectId: projectId);
                    },
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    ],
    errorBuilder: (context, state) => Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, size: 64, color: Colors.red),
            const SizedBox(height: 16),
            Text(
              'Page not found: ' + state.matchedLocation,
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: const Text('Go Home'),
            ),
          ],
        ),
      ),
    ),
  );
});

// Usage in widget
class DashboardScreen extends ConsumerWidget {
  const DashboardScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Dashboard',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => context.push('/dashboard/project/123'),
              child: const Text('View Project'),
            ),
            ElevatedButton(
              onPressed: () => context.push('/dashboard/project/456/settings'),
              child: const Text('Project Settings'),
            ),
          ],
        ),
      ),
    );
  }
}

More Flutter Rules

FLUTTER
92%

Flutter State Management with Riverpod

Use Riverpod for reactive state management with compile-time safety and testable provider architecture.

state-managementriverpodarchitecture
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../models/todo.dart';
im...
Mar 10, 2024by Remi Rousselet
View Rule
FLUTTER
90%

Flutter Clean Architecture Layers

Structure Flutter applications with domain, data, and presentation layers for maintainability.

flutterclean-architecturedomain-driven
// Domain Layer - Entities
class Article {
  final String id;
  final String title;
  final String content;
  final String authorId;
  final DateTime ...
Jan 20, 2024by Reso Coder
View Rule
FLUTTER
88%

Flutter BLoC Pattern Implementation

Implement BLoC for predictable state management with clear event-state separation.

flutterblocstate-management
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart...
Feb 18, 2024by Felix Angelov
View Rule