Navigator class

A widget that manages a set of child widgets with a stack discipline.

Many apps have a navigator near the top of their widget hierarchy in order to display their logical history using an Overlay with the most recently visited pages visually on top of the older pages. Using this pattern lets the navigator visually transition from one page to another by moving the widgets around in the overlay. Similarly, the navigator can be used to show a dialog by positioning the dialog widget above the current page.

Using the Pages API

The Navigator will convert its Navigator.pages into a stack of Routes if it is provided. A change in Navigator.pages will trigger an update to the stack of Routes. The Navigator will update its routes to match the new configuration of its Navigator.pages. To use this API, one can create a Page subclass and defines a list of Pages for Navigator.pages. A Navigator.onPopPage callback is also required to properly clean up the input pages in case of a pop.

By Default, the Navigator will use DefaultTransitionDelegate to decide how routes transition in or out of the screen. To customize it, define a TransitionDelegate subclass and provide it to the Navigator.transitionDelegate.

For more information on using the pages API, see the Router widget.

Using the Navigator API

Mobile apps typically reveal their contents via full-screen elements called "screens" or "pages". In Flutter these elements are called routes and they're managed by a Navigator widget. The navigator manages a stack of Route objects and provides two ways for managing the stack, the declarative API Navigator.pages or imperative API Navigator.push and Navigator.pop.

When your user interface fits this paradigm of a stack, where the user should be able to navigate back to an earlier element in the stack, the use of routes and the Navigator is appropriate. On certain platforms, such as Android, the system UI will provide a back button (outside the bounds of your application) that will allow the user to navigate back to earlier routes in your application's stack. On platforms that don't have this build-in navigation mechanism, the use of an AppBar (typically used in the Scaffold.appBar property) can automatically add a back button for user navigation.

Displaying a full-screen route

Although you can create a navigator directly, it's most common to use the navigator created by the Router which itself is created and configured by a WidgetsApp or a MaterialApp widget. You can refer to that navigator with Navigator.of.

A MaterialApp is the simplest way to set things up. The MaterialApp's home becomes the route at the bottom of the Navigator's stack. It is what you see when the app is launched.

void main() {
  runApp(const MaterialApp(home: MyAppHome()));
}

To push a new route on the stack you can create an instance of MaterialPageRoute with a builder function that creates whatever you want to appear on the screen. For example:

Navigator.push(context, MaterialPageRoute<void>(
  builder: (BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('My Page')),
      body: Center(
        child: TextButton(
          child: const Text('POP'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  },
));

The route defines its widget with a builder function instead of a child widget because it will be built and rebuilt in different contexts depending on when it's pushed and popped.

As you can see, the new route can be popped, revealing the app's home page, with the Navigator's pop method:

Navigator.pop(context);

It usually isn't necessary to provide a widget that pops the Navigator in a route with a Scaffold because the Scaffold automatically adds a 'back' button to its AppBar. Pressing the back button causes Navigator.pop to be called. On Android, pressing the system back button does the same thing.

Using named navigator routes

Mobile apps often manage a large number of routes and it's often easiest to refer to them by name. Route names, by convention, use a path-like structure (for example, '/a/b/c'). The app's home page route is named '/' by default.

The MaterialApp can be created with a Map<String, WidgetBuilder> which maps from a route's name to a builder function that will create it. The MaterialApp uses this map to create a value for its navigator's onGenerateRoute callback.

void main() {
  runApp(MaterialApp(
    home: const MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => const MyPage(title: Text('page A')),
      '/b': (BuildContext context) => const MyPage(title: Text('page B')),
      '/c': (BuildContext context) => const MyPage(title: Text('page C')),
    },
  ));
}

To show a route by name:

Navigator.pushNamed(context, '/b');

Routes can return a value

When a route is pushed to ask the user for a value, the value can be returned via the pop method's result parameter.

Methods that push a route return a Future. The Future resolves when the route is popped and the Future's value is the pop method's result parameter.

For example if we wanted to ask the user to press 'OK' to confirm an operation we could await the result of Navigator.push:

bool? value = await Navigator.push(context, MaterialPageRoute<bool>(
  builder: (BuildContext context) {
    return Center(
      child: GestureDetector(
        child: const Text('OK'),
        onTap: () { Navigator.pop(context, true); }
      ),
    );
  }
));

If the user presses 'OK' then value will be true. If the user backs out of the route, for example by pressing the Scaffold's back button, the value will be null.

When a route is used to return a value, the route's type parameter must match the type of pop's result. That's why we've used MaterialPageRoute<bool> instead of MaterialPageRoute<void> or just MaterialPageRoute. (If you prefer to not specify the types, though, that's fine too.)

Routes don't have to obscure the entire screen. PopupRoutes cover the screen with a ModalRoute.barrierColor that can be only partially opaque to allow the current screen to show through. Popup routes are "modal" because they block input to the widgets below.

There are functions which create and show popup routes. For example: showDialog, showMenu, and showModalBottomSheet. These functions return their pushed route's Future as described above. Callers can await the returned value to take an action when the route is popped, or to discover the route's value.

There are also widgets which create popup routes, like PopupMenuButton and DropdownButton. These widgets create internal subclasses of PopupRoute and use the Navigator's push and pop methods to show and dismiss them.

Custom routes

You can create your own subclass of one of the widget library route classes like PopupRoute, ModalRoute, or PageRoute, to control the animated transition employed to show the route, the color and behavior of the route's modal barrier, and other aspects of the route.

The PageRouteBuilder class makes it possible to define a custom route in terms of callbacks. Here's an example that rotates and fades its child when the route appears or disappears. This route does not obscure the entire screen because it specifies opaque: false, just as a popup route does.

Navigator.push(context, PageRouteBuilder<void>(
  opaque: false,
  pageBuilder: (BuildContext context, _, __) {
    return const Center(child: Text('My PageRoute'));
  },
  transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
    return FadeTransition(
      opacity: animation,
      child: RotationTransition(
        turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
        child: child,
      ),
    );
  }
));

The page route is built in two parts, the "page" and the "transitions". The page becomes a descendant of the child passed to the transitionsBuilder function. Typically the page is only built once, because it doesn't depend on its animation parameters (elided with _ and __ in this example). The transition is built on every frame for its duration.

(In this example, void is used as the return type for the route, because it does not return a value.)

Nesting Navigators

An app can use more than one Navigator. Nesting one Navigator below another Navigator can be used to create an "inner journey" such as tabbed navigation, user registration, store checkout, or other independent journeys that represent a subsection of your overall application.

Example

It is standard practice for iOS apps to use tabbed navigation where each tab maintains its own navigation history. Therefore, each tab has its own Navigator, creating a kind of "parallel navigation."

In addition to the parallel navigation of the tabs, it is still possible to launch full-screen pages that completely cover the tabs. For example: an on-boarding flow, or an alert dialog. Therefore, there must exist a "root" Navigator that sits above the tab navigation. As a result, each of the tab's Navigators are actually nested Navigators sitting below a single root Navigator.

In practice, the nested Navigators for tabbed navigation sit in the WidgetsApp and CupertinoTabView widgets and do not need to be explicitly created or managed.

The following example demonstrates how a nested Navigator can be used to present a standalone user registration journey.

Even though this example uses two Navigators to demonstrate nested Navigators, a similar result is possible using only a single Navigator.

Run this example with flutter run --route=/signup to start it with the signup flow instead of on the home page.

link

To create a local project with this code sample, run:
flutter create --sample=widgets.Navigator.1 mysample

import 'package:flutter/material.dart';

/// Flutter code sample for [Navigator].

void main() => runApp(const NavigatorExampleApp());

class NavigatorExampleApp extends StatelessWidget {
  const NavigatorExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // MaterialApp contains our top-level Navigator
      initialRoute: '/',
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => const HomePage(),
        '/signup': (BuildContext context) => const SignUpPage(),
      },
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.headlineMedium!,
      child: Container(
        color: Colors.white,
        alignment: Alignment.center,
        child: const Text('Home Page'),
      ),
    );
  }
}

class CollectPersonalInfoPage extends StatelessWidget {
  const CollectPersonalInfoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.headlineMedium!,
      child: GestureDetector(
        onTap: () {
          // This moves from the personal info page to the credentials page,
          // replacing this page with that one.
          Navigator.of(context)
              .pushReplacementNamed('signup/choose_credentials');
        },
        child: Container(
          color: Colors.lightBlue,
          alignment: Alignment.center,
          child: const Text('Collect Personal Info Page'),
        ),
      ),
    );
  }
}

class ChooseCredentialsPage extends StatelessWidget {
  const ChooseCredentialsPage({
    super.key,
    required this.onSignupComplete,
  });

  final VoidCallback onSignupComplete;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onSignupComplete,
      child: DefaultTextStyle(
        style: Theme.of(context).textTheme.headlineMedium!,
        child: Container(
          color: Colors.pinkAccent,
          alignment: Alignment.center,
          child: const Text('Choose Credentials Page'),
        ),
      ),
    );
  }
}

class SignUpPage extends StatelessWidget {
  const SignUpPage({super.key});

  @override
  Widget build(BuildContext context) {
    // SignUpPage builds its own Navigator which ends up being a nested
    // Navigator in our app.
    return Navigator(
      initialRoute: 'signup/personal_info',
      onGenerateRoute: (RouteSettings settings) {
        WidgetBuilder builder;
        switch (settings.name) {
          case 'signup/personal_info':
            // Assume CollectPersonalInfoPage collects personal info and then
            // navigates to 'signup/choose_credentials'.
            builder = (BuildContext context) => const CollectPersonalInfoPage();
          case 'signup/choose_credentials':
            // Assume ChooseCredentialsPage collects new credentials and then
            // invokes 'onSignupComplete()'.
            builder = (BuildContext _) => ChooseCredentialsPage(
                  onSignupComplete: () {
                    // Referencing Navigator.of(context) from here refers to the
                    // top level Navigator because SignUpPage is above the
                    // nested Navigator that it created. Therefore, this pop()
                    // will pop the entire "sign up" journey and return to the
                    // "/" route, AKA HomePage.
                    Navigator.of(context).pop();
                  },
                );
          default:
            throw Exception('Invalid route: ${settings.name}');
        }
        return MaterialPageRoute<void>(builder: builder, settings: settings);
      },
    );
  }
}

Navigator.of operates on the nearest ancestor Navigator from the given BuildContext. Be sure to provide a BuildContext below the intended Navigator, especially in large build methods where nested Navigators are created. The Builder widget can be used to access a BuildContext at a desired location in the widget subtree.

Finding the enclosing route

In the common case of a modal route, the enclosing route can be obtained from inside a build method using ModalRoute.of. To determine if the enclosing route is the active route (e.g. so that controls can be dimmed when the route is not active), the Route.isCurrent property can be checked on the returned route.

State Restoration

If provided with a restorationScopeId and when surrounded by a valid RestorationScope the Navigator will restore its state by recreating the current history stack of Routes during state restoration and by restoring the internal state of those Routes. However, not all Routes on the stack can be restored:

  • Page-based routes restore their state if Page.restorationId is provided.
  • Routes added with the classic imperative API (push, pushNamed, and friends) can never restore their state.
  • A Route added with the restorable imperative API (restorablePush, restorablePushNamed, and all other imperative methods with "restorable" in their name) restores its state if all routes below it up to and including the first Page-based route below it are restored. If there is no Page-based route below it, it only restores its state if all routes below it restore theirs.

If a Route is deemed restorable, the Navigator will set its Route.restorationScopeId to a non-null value. Routes can use that ID to store and restore their own state. As an example, the ModalRoute will use this ID to create a RestorationScope for its content widgets.

Inheritance

Constructors

Creates a widget that maintains a stack-based history of child widgets.
const

Properties

clipBehavior Clip
The content will be clipped (or not) according to this option.
final
hashCode int
The hash code for this object.
no setterinherited
initialRoute String?
The name of the first route to show.
final
key Key?
Controls how one widget replaces another widget in the tree.
finalinherited
observers List<NavigatorObserver>
A list of observers for this navigator.
final
onGenerateInitialRoutes RouteListFactory
Called when the widget is created to generate the initial list of Route objects if initialRoute is not null.
final
onGenerateRoute RouteFactory?
Called to generate a route for a given RouteSettings.
final
onPopPage PopPageCallback?
Called when pop is invoked but the current Route corresponds to a Page found in the pages list.
final
onUnknownRoute RouteFactory?
Called when onGenerateRoute fails to generate a route.
final
pages List<Page>
The list of pages with which to populate the history.
final
reportsRouteUpdateToEngine bool
Whether this navigator should report route update message back to the engine when the top-most route changes.
final
requestFocus bool
Whether or not the navigator and it's new topmost route should request focus when the new route is pushed onto the navigator.
final
restorationScopeId String?
Restoration ID to save and restore the state of the navigator, including its history.
final
routeTraversalEdgeBehavior TraversalEdgeBehavior
Controls the transfer of focus beyond the first and the last items of a focus scope that defines focus traversal of widgets within a route.
final
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
transitionDelegate TransitionDelegate
The delegate used for deciding how routes transition in or off the screen during the pages updates.
final

Methods

createElement() StatefulElement
Creates a StatefulElement to manage this widget's location in the tree.
inherited
createState() NavigatorState
Creates the mutable state for this widget at a given location in the tree.
override
debugDescribeChildren() List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug}) String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) String
Returns a one-line detailed description of the object.
inherited
toStringShort() String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited

Static Methods

canPop(BuildContext context) bool
Whether the navigator that most tightly encloses the given context can be popped.
defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) List<Route>
Turn a route name into a set of Route objects.
maybeOf(BuildContext context, {bool rootNavigator = false}) NavigatorState?
The state from the closest instance of this class that encloses the given context, if any.
maybePop<T extends Object?>(BuildContext context, [T? result]) Future<bool>
Consults the current route's Route.popDisposition getter or Route.willPop method, and acts accordingly, potentially popping the route as a result; returns whether the pop request should be considered handled.
of(BuildContext context, {bool rootNavigator = false}) NavigatorState
The state from the closest instance of this class that encloses the given context.
pop<T extends Object?>(BuildContext context, [T? result]) → void
Pop the top-most route off the navigator that most tightly encloses the given context.
popAndPushNamed<T extends Object?, TO extends Object?>(BuildContext context, String routeName, {TO? result, Object? arguments}) Future<T?>
Pop the current route off the navigator that most tightly encloses the given context and push a named route in its place.
popUntil(BuildContext context, RoutePredicate predicate) → void
Calls pop repeatedly on the navigator that most tightly encloses the given context until the predicate returns true.
push<T extends Object?>(BuildContext context, Route<T> route) Future<T?>
Push the given route onto the navigator that most tightly encloses the given context.
pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) Future<T?>
Push the given route onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true.
pushNamed<T extends Object?>(BuildContext context, String routeName, {Object? arguments}) Future<T?>
Push a named route onto the navigator that most tightly encloses the given context.
pushNamedAndRemoveUntil<T extends Object?>(BuildContext context, String newRouteName, RoutePredicate predicate, {Object? arguments}) Future<T?>
Push the route with the given name onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true.
pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, {TO? result}) Future<T?>
Replace the current route of the navigator that most tightly encloses the given context by pushing the given route and then disposing the previous route once the new route has finished animating in.
pushReplacementNamed<T extends Object?, TO extends Object?>(BuildContext context, String routeName, {TO? result, Object? arguments}) Future<T?>
Replace the current route of the navigator that most tightly encloses the given context by pushing the route named routeName and then disposing the previous route once the new route has finished animating in.
removeRoute(BuildContext context, Route route) → void
Immediately remove route from the navigator that most tightly encloses the given context, and Route.dispose it.
removeRouteBelow(BuildContext context, Route anchorRoute) → void
Immediately remove a route from the navigator that most tightly encloses the given context, and Route.dispose it. The route to be removed is the one below the given anchorRoute.
replace<T extends Object?>(BuildContext context, {required Route oldRoute, required Route<T> newRoute}) → void
Replaces a route on the navigator that most tightly encloses the given context with a new route.
replaceRouteBelow<T extends Object?>(BuildContext context, {required Route anchorRoute, required Route<T> newRoute}) → void
Replaces a route on the navigator that most tightly encloses the given context with a new route. The route to be replaced is the one below the given anchorRoute.
restorablePopAndPushNamed<T extends Object?, TO extends Object?>(BuildContext context, String routeName, {TO? result, Object? arguments}) String
Pop the current route off the navigator that most tightly encloses the given context and push a named route in its place.
restorablePush<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) String
Push a new route onto the navigator that most tightly encloses the given context.
restorablePushAndRemoveUntil<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) String
Push a new route onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true.
restorablePushNamed<T extends Object?>(BuildContext context, String routeName, {Object? arguments}) String
Push a named route onto the navigator that most tightly encloses the given context.
restorablePushNamedAndRemoveUntil<T extends Object?>(BuildContext context, String newRouteName, RoutePredicate predicate, {Object? arguments}) String
Push the route with the given name onto the navigator that most tightly encloses the given context, and then remove all the previous routes until the predicate returns true.
restorablePushReplacement<T extends Object?, TO extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, {TO? result, Object? arguments}) String
Replace the current route of the navigator that most tightly encloses the given context by pushing a new route and then disposing the previous route once the new route has finished animating in.
restorablePushReplacementNamed<T extends Object?, TO extends Object?>(BuildContext context, String routeName, {TO? result, Object? arguments}) String
Replace the current route of the navigator that most tightly encloses the given context by pushing the route named routeName and then disposing the previous route once the new route has finished animating in.
restorableReplace<T extends Object?>(BuildContext context, {required Route oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments}) String
Replaces a route on the navigator that most tightly encloses the given context with a new route.
restorableReplaceRouteBelow<T extends Object?>(BuildContext context, {required Route anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments}) String
Replaces a route on the navigator that most tightly encloses the given context with a new route. The route to be replaced is the one below the given anchorRoute.

Constants

defaultRouteName → const String
The name for the default route of the application.