How to navigate between screens in Flutter

Using Navigator 1.0, named routes, and GoRouter for declarative navigation in Flutter

, updated

Flutter offers multiple navigation approaches. This guide covers the basics and the modern recommended approach.

Basic navigation (Navigator 1.0)

The simplest way to push and pop screens:

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

// Pop back
Navigator.pop(context);

// Push with a result
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// Use result after pop

Named routes

Define routes in MaterialApp:

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/details': (context) => DetailScreen(),
    '/settings': (context) => SettingsScreen(),
  },
);

Navigate by name:

Navigator.pushNamed(context, '/details');

// With arguments
Navigator.pushNamed(context, '/details', arguments: {'id': 42});

// Extract arguments in the destination
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;

GoRouter is the Flutter team’s recommended routing solution for production apps. It supports deep linking, nested routes, and redirects.

Add the dependency:

dependencies:
  go_router: ^14.0.0

Define your router:

import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      path: '/details/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailScreen(id: id);
      },
    ),
    GoRoute(
      path: '/settings',
      builder: (context, state) => SettingsScreen(),
    ),
  ],
  // Redirect unauthenticated users
  redirect: (context, state) {
    final loggedIn = AuthService.isLoggedIn;
    final loggingIn = state.matchedLocation == '/login';
    if (!loggedIn && !loggingIn) return '/login';
    if (loggedIn && loggingIn) return '/';
    return null;
  },
);

void main() {
  runApp(MaterialApp.router(routerConfig: router));
}

Navigate with GoRouter:

// Push
context.go('/details/42');

// Push with extra data
context.go('/details', extra: {'title': 'Hello'});

// Go back
context.pop();

// Replace current route
context.replace('/settings');

Nested navigation with ShellRoute

For bottom navigation bars or side drawers that persist across pages:

final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) => ScaffoldWithNavBar(child: child),
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => HomeScreen(),
        ),
        GoRoute(
          path: '/search',
          builder: (context, state) => SearchScreen(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => ProfileScreen(),
        ),
      ],
    ),
  ],
);

Passing data between screens

// Via constructor (simplest)
Navigator.push(
  context,
  MaterialPageRoute(builder: (_) => DetailScreen(item: myItem)),
);

// Via GoRouter path parameters
context.go('/details/42');

// Via GoRouter extra
context.go('/details', extra: myItem);

// Via GoRouter query parameters
context.go('/search?q=flutter');
// In the screen:
final query = state.uri.queryParameters['q'];