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 popNamed 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 — declarative routing (recommended)
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.0Define 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'];