One of the crucial things every developer comes across when an app has more than two screens (which is true in most cases) is a need to navigate from one screen to another. Most apps contain several screens for different types of information. Sometimes you need to navigate from A -> B and sometimes, you have additional requirements.
These may be:
- Navigate from A->B
- Navigate from A->B with some Parameters
- Navigate from A->B with some Arguments
- Navigate from A->B with some Transition Animation
Flutter offers solutions for the requirements above with:
- A navigator widget
- Popular 3rd party Libraries
A widget that manages a set of child widgets with a stack discipline.
A MaterialPageRoute is useful because it transitions to the new route using a platform-specific animation.
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 the imperative API Navigator.push and Navigator.pop.
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 use CustomBuilderPage or create a Page subclass and define 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.
Lets See It In Action
We have few approaches for creating navigation between routes:
1. Navigate to a new screen and back
2. Navigate with named routes
3. Pass arguments to a named route
4. Return data from a screen
Navigate to a new screen and back
In Android, a route is equivalent to an Activity. In iOS, a route is equivalent to a ViewController. In Flutter, a route is just a widget.
Lets understand how it works by creating navigation between two routes, using these steps:
1. Create two routes.
2. Navigate to the second route using Navigator.push().
3. Return to the first route using Navigator.pop().
1. Create two routes
First, create two routes to work with. Since this is a basic example, each route contains only a single button. Tapping the button on the first route navigates to the second route. Tapping the button on the second route returns to the first route.
First, set up the visual structure:
2. Navigate to the second route using Navigator.push().
To switch to a new route, use the Navigator.push() method. The push() method adds a Route to the stack of routes managed by the Navigator. Where does the Route come from?
You can create your own, or use a MaterialPageRoute, which is useful because it transitions to the new route using a platform-specific animation.
In the build() method of the FirstRoute widget, update the onPressed() callback:
3. Return to the first route using Navigator.pop()
How do you close the second route and return to the first? By using the Navigator.pop() method. The pop() method removes the current Route from the stack of routes managed by the Navigator.
To implement a return to the original route, update the onPressed() callback in the SecondRoute widget:
Navigate with named routes
If you need to navigate to the same screen in many parts of your app, the above approach can result in code duplication. A better solution is to define a named route, and use the named route for navigation. To work with named routes, we use the Navigator.pushNamed() function.
Define the routes by providing additional properties to the MaterialApp constructor: the initialRoute and the routes themselves.
The initialRoute property defines which route the app should start with. The routes property defines the available named routes and the widgets to build when navigating to those routes.
Navigate to the second screen
With the widgets and routes in place, trigger navigation by using the Navigator.pushNamed() method. This tells Flutter to build the widget defined in the routes table and launch the screen.
Return to the first screen
To navigate back to the first screen, the Navigator.pop() function will be used as it is in the approach above.
Pass arguments to a named route
The Navigator provides the ability to navigate to a named route from any part of an app using a common identifier. In some cases, you might also need to pass arguments to a named route. For example, you might wish to navigate to the /user route and pass information about the user to that route.
You can accomplish this task using the arguments parameter of the Navigator.pushNamed() method. Extract the arguments using the ModalRoute.of() method or inside and Generate Route() function provided to the MaterialApp or CupertinoApp constructor.
Lets understand how it works, using these steps:
1. Define the arguments you need to pass.
2. Create a widget that extracts the arguments.
3. Register the widget in the routes table.
4. Navigate to the widget.
1. Define the arguments you need to pass
First, define the arguments you need to pass to the new route. In this example, pass two pieces of data: The Title of the Screen and a Message.
To pass both pieces of data, create a class that stores this information.
2. Create a widget that extracts the arguments
Next, create a widget that extracts and displays the title and message from the ScreenArguments. To access the ScreenArguments, use the ModalRoute.of() method. This method returns the current route with the arguments.
3. Register the widget in the routes table.
Next, add an entry to the routes provided to the MaterialApp widget. The routes define which widget should be created based on the name of the route.
4. Navigate to the widget
Finally, navigate to the ExtractArgumentsScreen when a user taps a button using Navigator.pushNamed(). Provide the arguments to the route via the arguments property. The ExtractArgumentsScreen extracts the title and message from these arguments.
Alternatively, extract the arguments using onGenerateRoute [Step 2 Alternative]
Instead of extracting the arguments directly inside the widget, you can also extract the arguments inside an onGenerateRoute() function and pass them to a widget. The onGenerateRoute() function creates the correct route based on the given RouteSettings.
Return data from a screen
In some cases, you might want to return data from a new screen. You can do this with the Navigator.pop() method.
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:
Additional Approaches for Navigation
1. Popup routes
2. Custom routes
3. Nesting Navigators
4. Hero Animations [Shared Element Transition]
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.
showDialog, showMenu, and showModalBottomSheet. These functions return their pushed route's Future as described above. Callers can wait for the returned value to take an action when the route is popped, or discover its 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.
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 behaviour 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.
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.
While using tabLayout 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.
Hero Animations [Shared Element Transition]
Flying an image from one screen to another is called a hero animation in Flutter, though the same motion is sometimes referred to as a shared element transition.
Basic structure of a hero animation
- Use two hero widgets in different routes but with matching tags to implement the animation.
- The Navigator manages a stack containing the app’s routes.
- Pushing a route on or popping a route from the Navigator’s stack triggers the animation.
- The Flutter framework calculates a rectangle tween, RectTween that defines the hero’s boundary as it flies from the source to the destination route. During its flight, the hero is moved to an application overlay, so that it appears on top of both routes.
Popular 3rd party libraries used for Navigation
Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.
- Simple route navigation
- Function handlers [map to a function instead of a route]
- Wildcard parameter matching
- Querystring parameter parsing
- Common transitions built-in
- Simple custom transition creation
router.navigateTo(context, "/users/1234", transition: TransitionType.fadeIn);
GetX is an extra-light and powerful solution for Flutter. It combines high performance state management, intelligent dependency injection, and route management in a quick and practical way.
GetX has 3 basic principles
- Performance: GetX is focused on performance and minimum resource consumption.
- Productivity: GetX uses a pleasant and easy syntax.
- Organization: GetX allows total decoupling of the View from the business logic.
Easy syntax: Get.to(OtherSereen())
A Flutter package for easy navigation management.
- Passing Parameters
- Passing Arguments
- Route Guards [Experimental]
- Pushing Multiple Routes
- Log Navigation
Provides an elegant abstraction for complete deep linking navigation in Flutter.
This package only provides deep linking for internal navigation. Any external platform-level deep linking solutions can be used optionally in conjunction with this package.