1. Initial route experience

What is a route? Routing is an abstraction of a screen or application page.

The main thing that Flutter allows us to manage routes gracefully is the Navigator class. This is a Widget for managing a set of pages that have certain rules of entry and exit, meaning that you can switch between pages regularly. The rule here is a “routing stack” that is maintained internally.

Those of you who have studied Android know that activities can be launched in a variety of ways to meet various business needs, and iOS also has built-in functionality. Flutter is one of the most promising cross-platform frameworks to incorporate the best of all worlds, and it is certainly capable of achieving native effects!

Let’s try to implement a small feature first.

1.1 Component Routing

When we first open the application, we see the first and lowest instance in the routing stack:

void main() {
  runApp(MaterialApp(home: Screen1()));
}
Copy the code

To push a new instance on the stack, we can call the Navigator Navigator. Push, passing in the current context and using the builder function to create the MaterialPageRoute instance, which creates what you want to display on the screen. Such as:

new RaisedButton(
   onPressed:(){
   Navigator.push(context, MaterialPageRoute<void>(
      builder: (BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('My Page')),
          body: Center(
            child: FlatButton(
              child: Text('POP'), onPressed: () { Navigator.pop(context); },),),); })); }, child:new Text("Push to Screen 2"),),Copy the code

Click to perform the above operations, we will successfully open the second page.

1.2 Named Routes

In general, we use the most named route, which is to use each page in the application to visit the name of a unique string, we can use this string to promote the page instance route.

For example, ‘/ home’ means HomeScreen and ‘/ login’ means LoginScreen. ‘/’ indicates the home page. The naming convention here is similar to routing in REST API development. So ‘/’ usually represents our root page.

Take a look at the following example:

new MaterialApp(
  home: new Screen1(),
  routes: <String, WidgetBuilder> {
    '/screen1': (BuildContext context) => new Screen1(),
    '/screen2' : (BuildContext context) => new Screen2(),
    '/screen3' : (BuildContext context) => new Screen3(),
    '/screen4' : (BuildContext context) => new Screen4()
  },
)
Copy the code

Screen1(), Screen2(), and so on are the class names for each page.

We can also implement the previous function:

new RaisedButton(
   onPressed:(){
     Navigator.of(context).pushNamed('/screen2');
   },
   child: new Text("Push to Screen 2"),),Copy the code

Or:

new RaisedButton(
   onPressed:(){
     Navigator.pushNamed(context, "/screen2")
   },
   child: new Text("Push to Screen 2"),),Copy the code

You can also do the top effect.

1.3 Pop

After the above two methods are implemented, the routing stack is as follows:

Now, when we want to go back to the home screen, we need to pop the Routes from the Navigator stack using the pop method.

Navigator.of(context).pop();
Copy the code

Explicit path pop-ups are usually not required when using scaffolds because that Scaffold automatically adds a “back” button to its AppBar that calls navigater.pop () when pressed.

In Android, pressing the device’s back button will do the same. However, it is possible that we might want to use this method for other components, such as the AlertDialog that pops up when the user clicks the cancel button.

Do not use ‘push’ instead of ‘pop’. Do you think I can do this in Screen2 and Screen1? In fact, it is not, see below:

So push is just to add an instance to the stack, pop pops an instance! (Except for special needs)

2. Detailed routing stack

Previously, we have seen how to simply push and pop instances in the routing stack, however, when faced with some special cases, this is clearly not sufficient. Those of you who have studied Android know that there are various startup modes of the Activity that can be used to fulfill these needs, but Flutter also has a similar implementation that can be used to address various business needs!

See the following usage methods and case studies.

2.1 pushReplacementNamed popAndPushNamed

RaisedButton(
  onPressed: () {
    Navigator.pushReplacementNamed(context, "/screen4");
  },
  child: Text("pushReplacementNamed"),
),
RaisedButton(
  onPressed: () {
    Navigator.popAndPushNamed(context, "/screen4");
  },
  child: Text("popAndPushNamed"),),Copy the code

We pushed Screen4 on Screen3 using pushReplacementNamed and popandPushnnamed.

In this case, the routing stack is as follows:

Screen4 took the place of Screen3.

PushReplacementNamed and popandPushnnamed PopAndPushNamed can perform Screen2’s popup animation and Screen3’s advance animation while pushReplacementNamed only shows Screen3’s advance animation.

Case study:

PushReplacementNamed: You don’t want the user to be able to return to LoginScreen when the user has successfully logged in and is now on HomeScreen. Therefore, the login should be completely replaced by the home page. Another example is going from SplashScreen to HomeScreen. It should be displayed only once and the user cannot return it from HomeScreen again. In this case, since we are entering a brand new screen, we may need to resort to this method.

PopAndPushNamed: Let’s say you have a Shopping application that displays a list of products in ProductsListScreen, and the user can apply filtered items in FiltersScreen. When the user clicks the Apply Filter button, FiltersScreen should pop up and push back to ProductsListScreen with the new filter value. PopAndPushNamed is obviously more appropriate.

2.2 pushNamedAndRemoveUntil

The user has logged in to HomeScreen and then went back to the interface after a series of operations to log out. You can’t just Push to LoginScreen, can you? You need to delete all instances from the previous route. Yes, users will not return to the previous route.

PushNamedAndRemoveUntil implements this function:

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);
Copy the code

Here (Route

Route) => false ensures that all previous instances are deleted.

Now there’s another requirement: we don’t want to delete all previous instances, we just want to delete a specified number of instances.

We have a shopping app that requires payment transactions. In the application, once the user completes the payment transaction, all pages related to the transaction or shopping cart should be removed from the stack, And the user should be taken to PaymentConfirmationScreen, click the back button should only put them back to ProductsListScreen or HomeScreen.

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', ModalRoute.withName('/screen1'));
Copy the code

Through the code, we push Screen4 and remove all routes until Screen1:

popUntil

Imagine that we have a set of information to fill out in an application, and the form is spread across multiple pages. Suppose you need to fill out a three-page form one step at a time. However, in part 3 of the form, the user cancelled filling out the form. The user clicks cancel and all previous pages related to the form should be brought up, and the user should be taken back to the HomeScreen or DashboardScreen, in which case the data belongs to the data invalid! We’re not going to push anything new here, we’re just going to go back to the routing stack.

Navigator.popUntil(context, ModalRoute.withName('/screen2'));
Copy the code

2.3 Popup Routes

The route doesn’t have to block the entire screen. PopupRoutes overrides the screen with the ModalRoute.Barriercolor, which is only partially opaque to allow the current screen to be displayed. The eject routes are “modal” because they block input to other components below.

There are several ways to create and display such pop-up routes. Examples are showDialog, showMenu, and showModalBottomSheet. As mentioned above, these functions return the Future of the route they push (asynchronous data, see the data section below). Execution can wait for the returned value to perform the operation when the route is ejected.

There are also components that create pop-up routes, such as PopupMenuButton and DropdownButton. These components create internal subclasses of PopupRoute and use the Navigator’s push and pop methods to display and close them.

2.4 User-defined Routes

You can create your own subclass of a window Z component library routing class (such as PopupRoute, ModalRoute, or PageRoute) to control the animation transitions used to display paths, the color and behavior of the paths ‘modal barriers, and various other characteristics of the paths.

The PageRouteBuilder class can define custom routes based on callbacks. Here is an example of rotating and diluting the children of a route as it appears or disappears. The route does not block the entire screen because opaque: false is specified. It is just like the route that is ejected.

Navigator.push(context, PageRouteBuilder(
  opaque: false,
  pageBuilder: (BuildContext context, _, __) {
    return 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, ), ); }));Copy the code

The route consists of two parts, “pageBuilder” and “transitionsBuilder”.

This page becomes the descendant of the child passed to buildTransitions method. Typically, the page is built only once, because it does not depend on its animation parameters (represented by _ and __ in this example). Transitions are built over the duration of each frame.

2.5 Setting methods

An application can use multiple route navigators. Nesting one navigator below another can be used to create “internal journeys,” such as tabbed navigation, user registration, store checkout, or other independent individuals that represent entire subsections of the application.

The standard practice for iOS applications is 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, you can launch full-screen pages that completely cover the tabs. Example: onboarding process or alert dialog box. Therefore, there must be a “root” navigator above the tabbed navigation. Therefore, Navigators for each TAB are really Navigators nested under a root navigator.

Nested navigators for tabbed navigation are located in WidgetApp and CupertinoTabView, so you don’t need to worry about nested navigators in this case, but it’s a real world example of using nested navigators.

The following example demonstrates how a nested Navigator can be used to render a separate user registration process.

Although this example uses two Navigators to demonstrate nested Navigators, you can get similar results with just one Navigato R.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      / /... some parameters omitted...
      // MaterialApp contains our top-level Navigator
      initialRoute: '/',
      routes: {
        '/': (BuildContext context) => HomePage(),
        '/signup': (BuildContext context) => SignUpPage(), }, ); }}class SignUpPage extends StatelessWidget {
 @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 _) => CollectPersonalInfoPage();
           break;
         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(); });break;
         default:
           throw Exception('Invalid route: ${settings.name}');
       }
       returnMaterialPageRoute(builder: builder, settings: settings); }); }}Copy the code

Of runs on the nearest root Navigator in a given BuildContext. Be sure to provide a BuildContext under the expected Navigator, especially in large build methods that create nested Navigators. The Builder component can be used to access the BuildContext at the desired location in the component subtree.

3. Data transfer between pages

3.1 Data Transfer

In most of the examples above, we push a new route without sending data, but this is rarely the case in real life. To send the data, we will use the Navigator to push the new MaterialPageRoute on the stack with our data (in this case, userName)

String userName = "John Doe";
Navigator.push(
    context,
    new MaterialPageRoute(
        builder: (BuildContext context) =>
        new Screen5(userName)));
Copy the code

To get the data in Screen5, all we need to do is add a parameterized constructor to Screen5:

class Screen5 extends StatelessWidget {

  final String userName;
  Screen5(this.userName);
  @override
  Widget build(BuildContext context) {
  print(userName) ... }}Copy the code

This means that we can use not only MaterialPageRoute as the push method, but also pushReplacement, pushAndPopUntil, etc. Basically from the route method we described above, the first parameter will now take the String of the MaterialPageRoute instead of namedRoute.

3.2 Data Return

We might also want to return data from the new page. Just like an alert application, and set a new tone for the alert, you will display a dialog box with a list of audio tone options. Obviously, once the dialog box pops up, you will need the selected project data. It can be implemented as follows:

new RaisedButton(onPressed: ()async{
  String value = await Navigator.push(context, new MaterialPageRoute<String>(
      builder: (BuildContext context) {
        return new Center(
          child: new GestureDetector(
              child: new Text('OK'),
              onTap: () { Navigator.pop(context, "Audio1"); })); }));print(value);

},
  child: new Text("Return"),Copy the code

inScreen4To try and check the console print value.

Also note that when a route is used for a return value, the type parameter of the route should match the result type of the POP. Here we need a String data, so we use MaterialPageRoute

. It doesn’t matter if you don’t specify a type.

4. Other effects

4.1 maybePop

Source:

static Future<bool> maybePop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).maybePop<T>(result);
  }

@optionalTypeArgs
  Future<bool> maybePop<T extends Object>([ T result ]) async {
    final Route<T> route = _history.last;
    assert(route._navigator == this);
    final RoutePopDisposition disposition = await route.willPop();
    if(disposition ! = RoutePopDisposition.bubble && mounted) {if (disposition == RoutePopDisposition.pop)
        pop(result);
      return true;
    }
    return false;
  }
Copy the code

What if we’re on the initial route and someone mistakenly tries to pop up this unique page? Popping the only page on the stack will close your application because there are no more pages behind it. This is obviously a bad experience. This is where maybePop() comes in. Clicking the maybePop button on Screen1 had no effect. Try the same thing on Screen3 and it pops up.

This effect can also be achieved with canPop:

4.2 canPop

Source:

static bool canPop(BuildContext context) {
    final NavigatorState navigator = Navigator.of(context, nullOk: true);
    returnnavigator ! =null && navigator.canPop();
  }

bool canPop() {
    assert(_history.isNotEmpty);
    return _history.length > 1 || _history[0].willHandlePopInternally;
  }
Copy the code

Return true if the occupied instance is greater than 1 or if the willHandlePopexchanges attribute is true, false otherwise.

We can determine whether the page canPop up by judging canPop.

4.3 How do I Remove the Default Back Button

AppBar({
    Key key,
    this.leading,
    this.automaticallyImplyLeading = true.this.title,
    this.actions,
    this.flexibleSpace,
    this.bottom,
    this.elevation = 4.0.this.backgroundColor,
    this.brightness,
    this.iconTheme,
    this.textTheme,
    this.primary = true.this.centerTitle,
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,
    this.toolbarOpacity = 1.0.this.bottomOpacity = 1.0,}) :assert(automaticallyImplyLeading ! =null),
       assert(elevation ! =null),
       assert(primary ! =null),
       assert(titleSpacing ! =null),
       assert(toolbarOpacity ! =null),
       assert(bottomOpacity ! =null),
       preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)),
       super(key: key);
Copy the code

Will automaticallyImplyLeading set to false

5. Reference links

Docs. Flutter. IO/flutter/wid…

Some of the pictures are from medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31

My Github: github.com/MeandNi

My blog: Meandni.com/