Flutter Navigation Guide

This guide covers everything you need to know about implementing navigation in your Flutter app, with a focus on authentication flows and route protection.

Table of Contents

Basic Navigation Setup

Flutter provides several ways to handle navigation. The most modern and recommended approach is using go_router, which offers declarative routing and deep linking support.

Setting Up GoRouter

import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginPage(),
    ),
    // Add more routes here
  ],
);

Using the Router in Your App

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'My App',
      routerConfig: router,
      theme: ThemeData(
        // Your theme configuration
      ),
    );
  }
}

Authentication Flow

Handling authentication in your navigation setup is crucial for protecting routes and managing user sessions.

Authentication Middleware

String? _runAuthMiddleware(context, state) {
  final currentUser = FirebaseAuth.instance.currentUser;
  if (currentUser == null) {
    return '/login';
  }
  return null;
}

Protected Routes Setup

GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    final isPublicRoute = state.matchedLocation == '/login' || 
                         state.matchedLocation == '/signup';
    
    if (!isPublicRoute && FirebaseAuth.instance.currentUser == null) {
      return '/login';
    }
    
    if (isPublicRoute && FirebaseAuth.instance.currentUser != null) {
      return '/';
    }
    
    return null;
  },
  routes: [
    // Your routes here
  ],
);

Route Protection

Public vs Protected Routes

  • Public Routes: Accessible without authentication

    • Login
    • Signup
    • Password Reset
    • Landing Pages
  • Protected Routes: Require authentication

    • Home
    • Profile
    • Settings
    • User-specific content

Implementing Route Guards

class RouteGuard {
  static bool isAuthenticated() {
    return FirebaseAuth.instance.currentUser != null;
  }

  static bool hasRequiredRole(String role) {
    // Implement role-based access control
    return true;
  }
}

State Management Integration

Bloc Integration with Navigation

GoRoute(
  path: '/profile',
  builder: (context, state) {
    return MultiBlocProvider(
      providers: [
        BlocProvider.value(value: authBloc),
        BlocProvider.value(value: profileBloc),
      ],
      child: const ProfilePage(),
    );
  },
);
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final GoRouter router;

  AuthBloc({required this.router}) : super(AuthInitial()) {
    on<AuthSuccess>((event, emit) {
      emit(AuthAuthenticated());
      router.go('/home');
    });
  }
}

Deep Linking

GoRouter(
  routes: [
    GoRoute(
      path: '/product/:id',
      builder: (context, state) {
        final productId = state.pathParameters['id'];
        return ProductPage(productId: productId);
      },
    ),
  ],
);
void configureDeepLinks() {
  FirebaseDynamicLinks.instance.onLink.listen((dynamicLinkData) {
    final Uri deepLink = dynamicLinkData.link;
    if (deepLink != null) {
      router.go(deepLink.path);
    }
  });
}

1. Route Organization

  • Group related routes together
  • Use nested routes for complex UIs
  • Keep route names consistent and meaningful

2. State Management

  • Use BlocProvider for state management
  • Avoid passing large objects through routes
  • Use query parameters for simple data

3. Error Handling

  • Implement a global error page
  • Handle navigation errors gracefully
  • Provide fallback routes

4. Performance

  • Use lazy loading for heavy pages
  • Implement route caching where appropriate
  • Monitor navigation performance

5. Testing

  • Test navigation flows
  • Verify route protection
  • Test deep linking scenarios

Common Navigation Patterns

Bottom Navigation

class MainScaffold extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: const RouterOutlet(),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _calculateSelectedIndex(context),
        onTap: (index) => _onItemTapped(index, context),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

Nested Navigation

GoRoute(
  path: '/dashboard',
  builder: (context, state) => const DashboardPage(),
  routes: [
    GoRoute(
      path: 'overview',
      builder: (context, state) => const OverviewPage(),
    ),
    GoRoute(
      path: 'analytics',
      builder: (context, state) => const AnalyticsPage(),
    ),
  ],
);

Troubleshooting

Common Issues and Solutions

  1. Route Not Found

    • Check route path spelling
    • Verify route registration
    • Ensure proper route hierarchy
  2. Authentication State Issues

    • Verify auth state listeners
    • Check redirect logic
    • Monitor auth state changes
  3. Deep Link Problems

    • Verify deep link configuration
    • Check URL scheme setup
    • Test deep link handling

Additional Resources