Skip to main content

Implementing Advanced Navigation

1. Using Navigator 2.0

Property Description Values
initialRoute Initial route to navigate to when the app starts /home
onGenerateRoute Function to generate a route based on the route name (RouteSettings settings) => Route
onUnknownRoute Function to handle unknown routes (RouteSettings settings) => Route

Example: Basic Navigator 2.0 Setup

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/',
      onGenerateRoute: (settings) {
        if (settings.name == '/') {
          return MaterialPageRoute(
            builder: (context) => HomeScreen(),
          );
        }
        return null;
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(child: Text('Welcome to Home Screen')),
    );
  }
}
Note: Navigator 2.0 introduces a new declarative navigation model that allows more control over the app's navigation stack.
Warning: Ensure that all routes are properly defined in onGenerateRoute or onUnknownRoute to prevent runtime errors.

2. Using RouterDelegate and RouteInformationParser

Class Description Usage
RouterDelegate Manages navigation state and builds the UI for a given route. Implement to control how routes are displayed and updated.
RouteInformationParser Parses route information from the URL and converts it into a RouteSettings object. Used to handle navigation based on URL changes in web apps.

Example: Implementing RouterDelegate

class MyRouterDelegate extends RouterDelegate<String> {
  final String initialRoute;

  MyRouterDelegate(this.initialRoute);

  @override
  Future<void> setNewRoutePath(String configuration) async {
    // Handle navigation to a new route
  }

  @override
  Future<void> popRoute() async {
    // Handle popping a route
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: this,
      routeInformationParser: MyRouteInformationParser(),
    );
  }
}

Example: Implementing RouteInformationParser

class MyRouteInformationParser extends RouteInformationParser<String> {
  @override
  Future<String> parseRouteInformation(RouteInformation routeInformation) async {
    return routeInformation.location ?? '/';
  }

  @override
  RouteInformation restoreRouteInformation(String configuration) {
    return RouteInformation(location: configuration);
  }
}
Note: RouterDelegate and RouteInformationParser are key to implementing custom navigation in Flutter, especially for web apps where URL-based routing is required.

3. Implementing Deep Linking

Deep linking in Flutter allows navigation to specific screens or states within an app using URLs or custom schemes. This is particularly useful for sharing content, integrating with external services, or enabling direct access to app features.

Feature Description Implementation
Deep Link URL URL that maps to a specific screen or state in the app. myapp://screen1?param=value
Custom Scheme App-specific URL scheme to handle deep links. myapp://
Universal Links (iOS) URLs that work both on iOS and web, using HTTPS. https://myapp.com/screen1
App Links (Android) URLs that work on Android devices using HTTPS. https://myapp.com/screen1
import 'package:flutter/material.dart';
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateRoute: (settings) {
        // Handle deep links here
        if (settings.name == '/screen1') {
          return MaterialPageRoute(
            builder: (context) => Screen1(),
          );
        }
        return null;
      },
    );
  }
}

class Screen1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Screen 1')),
      body: Center(child: Text('Deep Linked Screen')),
    );
  }
}
Note: For iOS, ensure your app's Info.plist includes the custom URL scheme. On Android, configure AndroidManifest.xml to handle intent filters.

Deep Linking Setup Steps

  • Define custom URL schemes in app configuration.
  • Implement route handling logic for incoming URLs.
  • Test deep links on both iOS and Android devices.

4. Using go_router Package

Feature Description Usage
go_router Modern, flexible routing solution for Flutter apps GoRouter class used to define routes and navigation logic
routes List of route definitions Each route is defined with name, path, and builder
go Method to navigate to a route context.go('/home')
goNamed Navigate to a named route context.goNamed('profile')
pop Go back to the previous route context.pop()

Example: Basic go_router setup

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      name: 'home',
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      name: 'profile',
      path: '/profile',
      builder: (context, state) => ProfileScreen(),
    ),
  ],
);

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => context.goNamed('profile'),
          child: Text('Go to Profile'),
        ),
      ),
    );
  }
}

Example: Navigation with parameters

final router = GoRouter(
  routes: [
    GoRoute(
      name: 'user',
      path: '/user/:id',
      builder: (context, state) {
        final userId = state.pathParameters['id']!;
        return UserScreen(userId: userId);
      },
    ),
  ],
);

class UserScreen extends StatelessWidget {
  final String userId;
  UserScreen({required this.userId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('User $userId')),
      body: Center(child: Text('User ID: $userId')),
    );
  }
}
Note: go_router provides better performance and more control than the default Navigator.
Warning: Ensure all routes are properly defined to avoid runtime errors.

5. Using auto_route Package

Feature Description Notes
auto_route ^7.8.0 Latest stable version as of 2026

Example: Setting up auto_route

// pubspec.yaml
dependencies:
  auto_route: ^7.8.0

dev_dependencies:
  auto_route_generator: ^7.8.0

Example: Defining Routes

@MaterialAutoRouter(
  routes: <AutoRoute>[
    AutoRoute(page: HomeScreen, initial: true),
    AutoRoute(page: ProfileScreen),
    AutoRoute(page: SettingsScreen),
  ],
)
class AppRouter {}

Example: Navigating with auto_route

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.pushRoute(ProfileScreenRoute());
          },
          child: Text('Go to Profile'),
        ),
      ),
    );
  }
}
Note: The auto_route package provides compile-time route generation, improving performance and type safety.
Warning: Ensure all route classes are properly annotated with @MaterialAutoRouter or @CupertinoAutoRouter.

Key Features of auto_route

  • Compile-time route generation
  • Type-safe navigation
  • Supports nested routes
  • Lazy loading support
  • Deep linking capabilities

6. Implementing Nested Navigation

Nested navigation in Flutter allows you to manage complex navigation hierarchies, such as having a bottom navigation bar with its own set of routes. This is particularly useful for apps that have multiple levels of navigation within a single screen or tab.

Property Description Example
Navigator.pushNamed Pushes a new route onto the navigation stack using a named route. Navigator.pushNamed(context, '/nestedRoute');
Navigator.pop Pops the current route off the navigation stack. Navigator.pop(context);
Navigator.pushReplacementNamed Replaces the current route with a new one using a named route. Navigator.pushReplacementNamed(context, '/newRoute');

Example: Nested Navigation with Bottom Navigation

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Main Screen')),
      body: Navigator(
        onGenerateRoute: (settings) {
          return MaterialPageRoute(
            builder: (context) => NestedScreen(),
          );
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
        onTap: (index) {
          if (index == 0) {
            Navigator.pushNamed(context, '/home');
          } else {
            Navigator.pushNamed(context, '/settings');
          }
        },
      ),
    );
  }
}

Example: Nested Route with TabBar

class NestedScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: Text('Nested Navigation'),
          bottom: TabBar(
            tabs: [
              Tab(text: 'Tab 1'),
              Tab(text: 'Tab 2'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Navigator(
              onGenerateRoute: (settings) {
                return MaterialPageRoute(
                  builder: (context) => TabOneScreen(),
                );
              },
            ),
            Navigator(
              onGenerateRoute: (settings) {
                return MaterialPageRoute(
                  builder: (context) => TabTwoScreen(),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}
Note: When using nested navigation, ensure that each navigator is properly scoped to avoid conflicts in route handling.

7. Creating Custom Route Transitions

Property Description Values
pageBuilder Function to build the page widget (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => Widget
transitionDuration Duration of the transition animation Duration
reverseTransitionDuration Duration of the reverse transition animation Duration

Example: Custom Slide Transition

class SlideTransitionPage extends PageRouteBuilder {
  final Widget page;

  SlideTransitionPage({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return SlideTransition(
              position: Tween<Offset>(
                begin: Offset(1.0, 0.0),
                end: Offset.zero,
              ).animate(animation),
              child: child,
            );
          },
        );
}

Example: Fade Transition

class FadeTransitionPage extends PageRouteBuilder {
  final Widget page;

  FadeTransitionPage({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return FadeTransition(
              opacity: Tween<double>(begin: 0.0, end: 1.0).animate(animation),
              child: child,
            );
          },
        );
}

8. Using PageRouteBuilder Widget

The PageRouteBuilder is a powerful widget in Flutter that allows you to create custom page transitions and animations. It provides fine-grained control over how a route is built and animated.

Property Description Values
pageBuilder Function that builds the page widget (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => Widget
transitionBuilder Function that builds the transition animation (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) => Widget
duration Duration of the transition animation Duration
reverseDuration Duration of the reverse transition animation Duration
fullscreenDialog Whether the route is a full-screen dialog bool

Example: Custom Page Transition

PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation) {
    return MyPage();
  },
  transitionBuilder: (context, animation, secondaryAnimation, child) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  },
  duration: Duration(milliseconds: 300),
)
Note: The PageRouteBuilder is particularly useful for creating complex custom transitions that are not available through the built-in route builders.

9. Implementing Hero Animations

Hero animations in Flutter provide a seamless transition between screens by animating a widget from one screen to another. This is particularly useful for navigating between detail and list views.

Property Description Values
tag Unique identifier for the Hero widget String
child The widget to animate Widget
transitionOnUserGestures Whether to allow user gestures to affect the transition bool

Example: Basic Hero Animation

import 'package:flutter/material.dart';

class HeroExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hero Example')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => HeroDetailScreen(),
              ),
            );
          },
          child: Hero(
            tag: 'heroImage',
            child: Image.asset('assets/images/hero.png'),
          ),
        ),
      ),
    );
  }
}

class HeroDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Hero Detail')),
      body: Center(
        child: Hero(
          tag: 'heroImage',
          child: Image.asset('assets/images/hero.png'),
        ),
      ),
    );
  }
}

Example: Hero with Custom Transition

class CustomHeroTransition extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Custom Hero')),
      body: Center(
        child: Hero(
          tag: 'customHero',
          transitionOnUserGestures: true,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}
Note: The tag must be unique within the navigation stack to ensure proper animation matching.
Warning: Hero animations do not work across different Navigator instances. Ensure all widgets are within the same navigation stack.

Hero Animation Best Practices

  • Use unique tags for each Hero widget to ensure proper matching.
  • Ensure the child widgets have similar shapes and sizes for smooth transitions.
  • Use transitionOnUserGestures to allow user interaction during animation.
  • Avoid Hero animations in complex layouts where widget sizes may vary significantly.

10. Managing Navigation Stack Programmatically

Method Description Usage
push Adds a new route to the top of the navigation stack navigator.push(MaterialPageRoute(builder: (context) => NewScreen()));
pop Removes the current route from the stack and navigates back navigator.pop();
popUntil Pops routes until a predicate route is found navigator.popUntil(ModalRoute.withName('/'));
replaceAll Replaces all routes in the stack with a new list of routes navigator.pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomeScreen()), ModalRoute.withName('/'));
removeRoute Removes a specific route from the stack navigator.removeRoute(route);
Note: Use Navigator.of(context) to access the current navigator instance.
Warning: Avoid modifying navigation stack directly in stateful widgets without proper checks to prevent unexpected behavior.

11. Implementing Bottom Navigation with Routes

Property Description Example
BottomNavigationBar Widget for creating bottom navigation bar BottomNavigationBar(items: [ ... ])
BottomNavigationBarItem Represents a single item in the bottom nav bar BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home')
currentIndex Index of the currently selected item currentIndex: _selectedIndex
onTap Callback when an item is tapped onTap: (index) { setState(() { _selectedIndex = index; }); }

Example: Basic Bottom Navigation Setup

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  final List<Widget> _pages = [
    HomeScreen(),
    SearchScreen(),
    ProfileScreen(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bottom Navigation Example')),
      body: _pages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Home Screen'));
  }
}

class SearchScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Search Screen'));
  }
}

class ProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Profile Screen'));
  }
}
Note: The currentIndex must be updated via setState to reflect the selected item.
Warning: Make sure each page in _pages list corresponds to the correct index in BottomNavigationBarItem.

Key Points

  • BottomNavigationBar is used to navigate between different sections of an app.
  • Each BottomNavigationBarItem represents a tab in the navigation bar.
  • The currentIndex property determines which item is currently selected.
  • Use onTap to handle navigation between pages.

Was this article helpful?