Flutter Advanced Navigation
Master complex navigation patterns and techniques
đ What is Advanced Navigation?
Advanced Navigation encompasses sophisticated routing techniques including nested navigation, custom transitions, route guards, passing complex data, returning results from screens, and managing navigation state. These patterns enable building complex, production-ready applications with seamless user experiences and maintainable code architecture.
// Advanced navigation example
Navigator.push(context, PageRouteBuilder(...))
Key Advanced Navigation Concepts
Custom Transitions
Create animated page transitions
PageRouteBuilder(
transitionsBuilder: ...
)
Return Data
Get results from screens
final result = await
Navigator.push(...)
Route Guards
Protect routes with conditions
onGenerateRoute: (settings) {
if (!isLoggedIn) return ...
}
Nested Navigation
Multiple navigation stacks
Navigator(
key: navigatorKey,
onGenerateRoute: ...
)
đš Custom Page Transitions
Create smooth, custom animations when navigating:
// Slide transition
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0); // Start from right
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(
CurveTween(curve: curve),
);
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
// Fade transition
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
),
);
// Scale transition
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: animation,
child: child,
);
},
),
);
Transition Types:
â Slide: Page slides in from a direction
â Fade: Page fades in smoothly
â Scale: Page grows from center
đš Returning Data from Screens
Get results back from a screen after navigation:
// Screen 1: Navigate and wait for result
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () async {
// Navigate and wait for result
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// Use the returned result
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('You selected: $result')),
);
}
},
child: Text('Select an Option'),
),
),
);
}
}
// Screen 2: Return data when going back
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// Return data to previous screen
Navigator.pop(context, 'Option A');
},
child: Text('Option A'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Option B');
},
child: Text('Option B'),
),
],
),
),
);
}
}
Use Cases:
- Selecting items from a list
- Form submission results
- User confirmations
- Settings changes
đš Route Guards (Authentication)
Protect routes and redirect based on conditions:
class MyApp extends StatelessWidget {
final bool isLoggedIn = false; // Check authentication status
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Route Guards Demo',
onGenerateRoute: (settings) {
// Check if user is trying to access protected route
if (settings.name == '/profile' || settings.name == '/settings') {
if (!isLoggedIn) {
// Redirect to login if not authenticated
return MaterialPageRoute(
builder: (context) => LoginScreen(),
);
}
}
// Allow navigation if authenticated or public route
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomeScreen());
case '/login':
return MaterialPageRoute(builder: (context) => LoginScreen());
case '/profile':
return MaterialPageRoute(builder: (context) => ProfileScreen());
case '/settings':
return MaterialPageRoute(builder: (context) => SettingsScreen());
default:
return MaterialPageRoute(builder: (context) => NotFoundScreen());
}
},
);
}
}
đš Passing Complex Data
Pass objects and complex data between screens:
// Define a data class
class User {
final String name;
final String email;
final int age;
User({required this.name, required this.email, required this.age});
}
// Screen 1: Pass complex object
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Create user object
final user = User(
name: 'John Doe',
email: '[email protected]',
age: 30,
);
// Navigate with object
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfileScreen(user: user),
),
);
},
child: Text('View Profile'),
),
),
);
}
}
// Screen 2: Receive complex object
class ProfileScreen extends StatelessWidget {
final User user;
ProfileScreen({required this.user});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Profile')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${user.name}', style: TextStyle(fontSize: 20)),
SizedBox(height: 10),
Text('Email: ${user.email}', style: TextStyle(fontSize: 20)),
SizedBox(height: 10),
Text('Age: ${user.age}', style: TextStyle(fontSize: 20)),
],
),
),
);
}
}
đš Nested Navigation
Create independent navigation stacks (e.g., for bottom navigation):
class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State {
int _currentIndex = 0;
// Create separate navigator keys for each tab
final List> _navigatorKeys = [
GlobalKey(),
GlobalKey(),
GlobalKey(),
];
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Handle back button for nested navigators
return !await _navigatorKeys[_currentIndex].currentState!.maybePop();
},
child: Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
_buildNavigator(0, HomeFlow()),
_buildNavigator(1, SearchFlow()),
_buildNavigator(2, ProfileFlow()),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}
Widget _buildNavigator(int index, Widget child) {
return Navigator(
key: _navigatorKeys[index],
onGenerateRoute: (settings) {
return MaterialPageRoute(builder: (context) => child);
},
);
}
}
class HomeFlow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeDetailsScreen()),
);
},
child: Text('Go to Home Details'),
),
),
);
}
}
Benefits of Nested Navigation:
- Each tab maintains its own navigation stack
- Switching tabs preserves navigation history
- Better user experience in complex apps
đš Hero Animations
Create smooth shared element transitions:
// Screen 1: Wrap widget with Hero
class ListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Items')),
body: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return ListTile(
leading: Hero(
tag: 'item-$index', // Unique tag
child: CircleAvatar(
child: Text('${index + 1}'),
),
),
title: Text('Item ${index + 1}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(index: index),
),
);
},
);
},
),
);
}
}
// Screen 2: Use same Hero tag
class DetailScreen extends StatelessWidget {
final int index;
DetailScreen({required this.index});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: Hero(
tag: 'item-$index', // Same tag as source
child: CircleAvatar(
radius: 100,
child: Text('${index + 1}', style: TextStyle(fontSize: 50)),
),
),
),
);
}
}
Result:
â Avatar smoothly animates from list to detail screen
â Creates a connected, fluid experience
đš Complete Example
A comprehensive advanced navigation implementation:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Advanced Navigation',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Advanced Navigation')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final result = await Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
SelectionScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
),
);
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Selected: $result')),
);
}
},
child: Text('Custom Transition + Return Data'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HeroListScreen(),
),
);
},
child: Text('Hero Animation Demo'),
),
],
),
),
);
}
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Make a Selection')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Navigator.pop(context, 'Option 1'),
child: Text('Option 1'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.pop(context, 'Option 2'),
child: Text('Option 2'),
),
],
),
),
);
}
}
class HeroListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero List')),
body: ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return ListTile(
leading: Hero(
tag: 'hero-$index',
child: CircleAvatar(child: Icon(Icons.star)),
),
title: Text('Item ${index + 1}'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HeroDetailScreen(index: index),
),
);
},
);
},
),
);
}
}
class HeroDetailScreen extends StatelessWidget {
final int index;
HeroDetailScreen({required this.index});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail')),
body: Center(
child: Hero(
tag: 'hero-$index',
child: Icon(Icons.star, size: 200, color: Colors.amber),
),
),
);
}
}