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),
        ),
      ),
    );
  }
}

🧠 Test Your Knowledge

Which widget is used to create custom page transitions?