Navigation and Routing

Flutter provides two navigation systems: the classic Navigator and the newer GoRouter for declarative routing.

Dart
// Push a new screen
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => DetailScreen(id: 42)),
);

// Push and wait for result
final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(builder: (context) => SearchScreen()),
);
print('User selected: $result');

// Pop back
Navigator.pop(context);

// Pop with result
Navigator.pop(context, 'selected_item');

// Replace current screen (no back button)
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => HomeScreen()),
);

// Clear stack and go to a screen
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => LoginScreen()),
  (route) => false,  // Remove all routes
);
YAML
dependencies:
  go_router: ^13.0.0
Dart
// lib/router.dart
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: '/courses',
      builder: (context, state) => const CourseListScreen(),
      routes: [
        GoRoute(
          path: ':courseId',
          builder: (context, state) => CourseDetailScreen(
            courseId: state.pathParameters['courseId']!,
          ),
          routes: [
            GoRoute(
              path: ':lessonId',
              builder: (context, state) => LessonScreen(
                courseId: state.pathParameters['courseId']!,
                lessonId: state.pathParameters['lessonId']!,
              ),
            ),
          ],
        ),
      ],
    ),
    GoRoute(
      path: '/profile',
      builder: (context, state) => const ProfileScreen(),
    ),
  ],
);
Dart
// main.dart
import 'router.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'My App',
      routerConfig: router,
    );
  }
}
Dart
// Simple navigation
context.go('/courses');
context.push('/courses/flutter/intro');

// Navigate with extra data
context.push('/profile', extra: UserProfile(name: 'Alice'));

// Replace current route
context.go('/courses');

// Go back
context.pop();

// Get current route info
final courseId = state.pathParameters['courseId'];
final queryParams = state.uri.queryParameters;

Passing Data Between Screens

Dart
// Method 1: Constructor parameters
class DetailScreen extends StatelessWidget {
  final String id;
  final String title;
  
  const DetailScreen({required this.id, required this.title});

  @override
  Widget build(BuildContext context) => Scaffold(body: Text(title));
}

// Push: DetailScreen(id: '1', title: 'My Item')

// Method 2: Route extra data
context.push('/detail', extra: {'id': '1', 'title': 'My Item'});

// Method 3: Named routes with parameters
// /courses/:courseId — state.pathParameters['courseId']

Bottom Navigation

Dart
class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _currentIndex = 0;

  final _screens = [
    const HomeScreen(),
    const SearchScreen(),
    const ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _screens[_currentIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: _currentIndex,
        onDestinationSelected: (index) => setState(() => _currentIndex = index),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
          NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
          NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

Page Transitions

Dart
// Custom transition
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      final tween = Tween(begin: Offset(0, 1), end: Offset.zero)
          .chain(CurveTween(curve: Curves.easeInOut));
      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  ),
);

// GoRouter custom transition
GoRoute(
  path: '/detail',
  pageBuilder: (context, state) => CustomTransitionPage(
    child: DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(opacity: animation, child: child);
    },
  ),
)

💡 Tip: Use GoRouter for new projects — it handles deep linking, redirects, and nested routes cleanly.

Next: Networking and APIs