Перейти к основному содержимому

Flutter Navigation Guide

A comprehensive guide to Flutter navigation patterns with detailed explanations, use cases, and practical examples. This guide covers both imperative and declarative navigation approaches.

Table of Contents​

Understanding Navigation in Flutter​

What is Navigation? Navigation in Flutter refers to the process of moving between different screens (pages) in your app. Flutter provides a stack-based navigation system where screens are pushed onto and popped off a navigation stack.

Key Concepts:

  • Navigation Stack: A stack of screens where the top screen is currently visible
  • Push: Add a new screen to the top of the stack
  • Pop: Remove the current screen from the top of the stack
  • Route: A screen or page in your app

Imperative Navigation:

  • Direct control over navigation stack
  • Programmatic navigation using Navigator class
  • Good for complex navigation logic

Declarative Navigation:

  • Route-based navigation using named routes
  • Centralized route definitions
  • Better for deep linking and web URLs

Imperative Navigation​

Basic Navigation Operations​

Push (Navigate Forward)​

What it does: Adds a new screen to the navigation stack.

When to use:

  • Moving to a detail screen
  • Opening forms or modals
  • Any forward navigation where you want to return
// Basic push navigation
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsPage(),
),
);
},
child: const Text('Open Details'),
)

// Push with custom route settings
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailsPage(),
settings: RouteSettings(name: '/details'),
fullscreenDialog: true, // Shows as modal on iOS
),
)

Pop (Navigate Back)​

What it does: Removes the current screen from the navigation stack.

When to use:

  • Returning to the previous screen
  • Closing modals or dialogs
  • Canceling operations
// Basic pop navigation
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Back'),
)

// Pop with data (returning data to previous screen)
ElevatedButton(
onPressed: () => Navigator.of(context).pop('Selected Item'),
child: const Text('Select and Return'),
)

// Pop with multiple screens (go back multiple levels)
ElevatedButton(
onPressed: () => Navigator.of(context).popUntil((route) => route.isFirst),
child: const Text('Go to Home'),
)

Advanced Navigation Operations​

PushReplacement (Replace Current Screen)​

What it does: Replaces the current screen with a new one, removing the current screen from the stack.

When to use:

  • Login/logout flows
  • Splash screen to main app
  • When you don't want users to go back to the previous screen
// Replace current screen
ElevatedButton(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
},
child: const Text('Go to Home'),
)

// Replace with custom settings
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const HomePage(),
settings: RouteSettings(name: '/home'),
),
)

PushAndRemoveUntil (Clear Stack)​

What it does: Pushes a new screen and removes all previous screens from the stack based on a condition.

When to use:

  • After successful login (clear login screens)
  • App initialization flows
  • When you want to prevent back navigation to certain screens
// Clear entire stack and go to home
ElevatedButton(
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const HomePage(),
),
(route) => false, // Remove all routes
);
},
child: const Text('Login and Go Home'),
)

// Clear stack until a specific route
ElevatedButton(
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => const ProfilePage(),
),
(route) => route.settings.name == '/home', // Keep home route
);
},
child: const Text('Go to Profile'),
)

PopUntil (Go Back to Specific Screen)​

What it does: Pops screens from the stack until a specific condition is met.

When to use:

  • Going back to a specific screen in the stack
  • Clearing intermediate screens
  • Navigation breadcrumbs
// Go back to home screen
ElevatedButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: const Text('Go to Home'),
)

// Go back to specific named route
ElevatedButton(
onPressed: () {
Navigator.of(context).popUntil((route) => route.settings.name == '/profile');
},
child: const Text('Go to Profile'),
)

Named Routes​

Setting Up Named Routes​

What it does: Defines routes by name for centralized navigation management.

When to use:

  • Large apps with many screens
  • Deep linking support
  • Web URL routing
  • Consistent navigation patterns
// Define routes in MaterialApp
MaterialApp(
title: 'My App',
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/login': (context) => const LoginPage(),
'/profile': (context) => const ProfilePage(),
'/details': (context) => const DetailsPage(),
'/settings': (context) => const SettingsPage(),
},
// Optional: Handle unknown routes
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => const NotFoundPage(),
);
},
)

Basic Named Route Navigation​

// Navigate to named route
ElevatedButton(
onPressed: () => Navigator.pushNamed(context, '/details'),
child: const Text('Go to Details'),
)

// Navigate and replace current screen
ElevatedButton(
onPressed: () => Navigator.pushReplacementNamed(context, '/home'),
child: const Text('Go to Home'),
)

// Navigate and clear stack
ElevatedButton(
onPressed: () => Navigator.pushNamedAndRemoveUntil(
context,
'/home',
(route) => false,
),
child: const Text('Login and Go Home'),
)

Named Routes with Arguments​

// Define route with arguments
MaterialApp(
routes: {
'/': (context) => const HomePage(),
'/details': (context) => const DetailsPage(),
},
// Handle routes with arguments
onGenerateRoute: (settings) {
if (settings.name == '/details') {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (context) => DetailsPage(
id: args['id'],
title: args['title'],
),
);
}
return null;
},
)

// Navigate with arguments
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/details',
arguments: {
'id': 123,
'title': 'Product Details',
},
);
},
child: const Text('View Details'),
)

// Access arguments in the destination screen
class DetailsPage extends StatelessWidget {
final int id;
final String title;

const DetailsPage({
Key? key,
required this.id,
required this.title,
}) : super(key: key);


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Text('Details for item $id'),
),
);
}
}

Advanced Navigation Patterns​

Full-Screen Dialog​

What it does: Presents a screen as a modal dialog.

When to use:

  • Login screens
  • Important forms
  • Confirmation dialogs
// Present as full-screen dialog
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const LoginPage(),
fullscreenDialog: true, // Shows as modal on iOS
),
)

Bottom Sheet​

What it does: Slides up from the bottom of the screen.

When to use:

  • Quick actions
  • Selection menus
  • Additional options
// Show bottom sheet
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 200,
child: Column(
children: [
ListTile(
leading: Icon(Icons.edit),
title: Text('Edit'),
onTap: () {
Navigator.pop(context);
// Handle edit action
},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () {
Navigator.pop(context);
// Handle delete action
},
),
],
),
),
);
},
child: const Text('Show Options'),
)

Tab Navigation​

TabBar with TabBarView​

class TabNavigationPage extends StatefulWidget {

_TabNavigationPageState createState() => _TabNavigationPageState();
}

class _TabNavigationPageState extends State<TabNavigationPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;


void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tab Navigation'),
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.search), text: 'Search'),
Tab(icon: Icon(Icons.person), text: 'Profile'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
HomeTab(),
SearchTab(),
ProfileTab(),
],
),
);
}


void dispose() {
_tabController.dispose();
super.dispose();
}
}

Passing Data Forward​

Constructor Parameters​

// Pass data through constructor
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsPage(
itemId: 123,
itemTitle: 'Product Name',
),
),
);
},
child: const Text('View Details'),
)

// Receive data in destination screen
class DetailsPage extends StatelessWidget {
final int itemId;
final String itemTitle;

const DetailsPage({
Key? key,
required this.itemId,
required this.itemTitle,
}) : super(key: key);


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(itemTitle)),
body: Center(
child: Text('Details for item $itemId'),
),
);
}
}

Returning Data Back​

Pop with Data​

// Return data when popping
ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Selected Option');
},
child: const Text('Select Option'),
)

// Receive returned data
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SelectionPage(),
),
);

if (result != null) {
print('Selected: $result');
// Handle the returned data
}
},
child: const Text('Open Selection'),
)

Awaiting Navigation Results​

// Wait for navigation result
Future<void> _showConfirmationDialog() async {
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Confirm Action'),
content: Text('Are you sure you want to proceed?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Confirm'),
),
],
),
);

if (result == true) {
// User confirmed the action
_performAction();
}
}

Performance Optimization​

Lazy Loading​

// Use lazy loading for heavy screens
MaterialApp(
routes: {
'/': (context) => const HomePage(),
'/heavy-screen': (context) => const HeavyScreen(),
},
onGenerateRoute: (settings) {
// Lazy load heavy screens
if (settings.name == '/heavy-screen') {
return MaterialPageRoute(
builder: (context) => FutureBuilder(
future: Future.delayed(Duration(milliseconds: 100)),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return const HeavyScreen();
}
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
},
),
);
}
return null;
},
)

Route Guards​

// Implement route guards for authentication
MaterialApp(
onGenerateRoute: (settings) {
// Check authentication for protected routes
if (_isProtectedRoute(settings.name) && !_isAuthenticated()) {
return MaterialPageRoute(
builder: (context) => const LoginPage(),
);
}

// Return normal route
return _getRoute(settings);
},
)

bool _isProtectedRoute(String? routeName) {
return ['/profile', '/settings', '/admin'].contains(routeName);
}

bool _isAuthenticated() {
// Check authentication status
return authService.isLoggedIn;
}

Error Handling​

Handle Navigation Errors​

// Safe navigation with error handling
Future<void> _safeNavigate(String routeName) async {
try {
await Navigator.pushNamed(context, routeName);
} catch (e) {
// Handle navigation errors
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Navigation failed: $e'),
backgroundColor: Colors.red,
),
);
}
}

Track Navigation State​

class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

static NavigatorState get navigator => navigatorKey.currentState!;

static Future<dynamic> pushNamed(String routeName, {Object? arguments}) {
return navigator.pushNamed(routeName, arguments: arguments);
}

static void pop([dynamic result]) {
return navigator.pop(result);
}
}

// Use in MaterialApp
MaterialApp(
navigatorKey: NavigationService.navigatorKey,
routes: {
'/': (context) => const HomePage(),
'/details': (context) => const DetailsPage(),
},
)

// Use anywhere in the app
ElevatedButton(
onPressed: () => NavigationService.pushNamed('/details'),
child: const Text('Go to Details'),
)

References​