The overhaul of Navigator 2.0 is a really big one, and while it doesn’t affect the previous code, we can see the extent of the changes through the official design guidelines. I was going to write a summary, but I found the official one pretty good.

Past wonderful

👉 Flutter will know will series – start Overlay of Navigator

👉 Flutter will know will series — Unsung Heroes _Theatre

👉 Flutter must know must Know series – fully understand the Route

👉 Flutter must Know must Know series – Explore the Route page opening process

Summary

Introduces a declarative API to set the Navigator’s history stack and introduces a new Router component. The Router component configures the Navigator based on application status and system events.

Objective

This article introduces a new API for Navigator that allows you to set the Navigator’s history stack declaratively, with the key being the immutable group Page. We know that the Navigator manages a Route, so the Page is converted to a Route. The relationship between Page and Route is similar to the relationship between Widget and Element. The documentation also describes how existing imperative Navigator apis (push, POP, Replace, and so on) have been refactored to work with the new declarative API.

Finally, the article introduces a newly introduced component, the Router, that wraps around the Navigator component. The Router component configures a set of pages, which are then displayed by the Navigator component. Of course, the new Navigator responds to events from the system and application state. While the program is running, the Router component can reconfigure the Navigator if it receives a new intent, such as a jump. When the system’s back key is clicked, the Router component removes the route at the top of the stack to move back. The Router component can also configure the application Navigator to respond to user input, and so on.

Goals

  • Developers can set and modify them declarativelyNavigatorHistory page stack
  • Existing imperativeAPI pushEtc will not be affected, and can and newAPICompatible with
  • The developer willNavigatorThe imperative history stack in the new declarative way
  • RouterComponents can be wrappedNavigatorThe component can also reconfigure the stack based on application state and system events:
  • The Router component configures the Navigator to display the initial route when the application starts
  • When receiving the intention to display the route, the Router can again configure the component Navigator to display the page.
  • When the user clicks the system Back button, the Router can reconfigure the Navigator component to update the top-level route for the backward effect.
  • The developer can delegate the Router component to reconfigure the Navigator’s historical routing stack to display new routes in response to user interactions
  • Developers can customize the routing behavior, such as name versus page alignment
  • Routers/NavigatorsCan be nested, press the system back button to eject routes from the most appropriate navigator

Background & Motivation

This section discusses the capabilities and drawbacks of the older Navigator. Flutter provides two ways to configure and modify the Navigator’s historical routing stack: the initialRoute property and the imperative API (push, POP, pushNamed, pushAndRemoveUntil, etc.).

Initial Route

The initialRoute parameter only works when the Navigator is first built, usually by setting window.defaultroutename, and the application starts to display the route page corresponding to the name. However, it is not effective to modify the initialRoute parameter again after the Navigator has built. In this case, there is no good way for the developer to do this while the application is running because the developer cannot easily replace the routing history stack.

Imperative API

Navigator’s imperative API is a static method in Navigator that obtains an instance of NavigatorState. These apis allow developers to push new routes and remove old ones, and developers call these apis in response to user interactions, such as when the user clicks the back button on the AppBar and the developer calls the POP method to remove the route at the top of the stack. When the user views the element’s detail page, the developer calls the push method to add a new detail page route to the top of the stack. As mentioned earlier, the imperative API makes each change to the routing stack very specific and loses some flexibility, for example, the developer cannot freely modify and rearrange the routing stack. Therefore, developers must extend existing apis to fulfill certain requirements. Essentially, these requirements or flexibilities require stack control over the historical path.

In Flutter research, there was also feedback from developers that the imperative API did not match the Flutter style. In Flutter, if you want to set the display of the Widget’s child nodes, you can simply rebuild the Widget using the new child nodes. However, this cannot be done in the Navigator. You have to call the clunky imperative API.

Nested Navigators

Another sore point with older Versions of Navigator is the nesting of Navigators. Nested Navigators are common in Tab pages, with a separate Navigator underneath each Tab, Separate navigators are then nested under the root Navigator. Nested Navigator tracks the historical routing stack. The old version of Flutter had only the root Navigator associated with the system return key. This could cause confusion if the developer routed to a page within a Tab: Clicking the system Back key returns you to the previous page, not the Tab’s history stack, and taints the global history stack.

Overview

This section gives an overview of the Navigator declarative API and the newly introduced Router component, and the next section explains the implementation in more detail.

Navigator

This section introduces the concept of Page and divides Navigator’s routing management into two groups: Page and non-page routes.

Pages

To be able to declaratively set the Navigator’s history stack, the developer needs to provide the Navigator with a set of Page objects via the Navigator constructor. The Page array is immutable and describes the route that should be placed in the Navigator stack. Navigator inflates a Page object into a routing object. In this respect, the relationship between Page and Route is like that between Widget and Element: Widget and Page are just configurations.

The Page object can be converted into a corresponding Route object, which can be placed in the history routing stack. But not every Route has a Page corresponding to it.

Developers are free to implement their own Page objects or use the PageBuilders provided by the Framework. The Framework uses PageBuilders to get routes or widgets within them. A specific PageBuilder will Route the corresponding Widget, such as the MaterialPageRoute.

The Routes corresponding to Pages in the history routing stack are in the same order as their corresponding Pages in the list provided to the Navigator. If the Page list set to the Navigator changes, the new list is compared to the old list, and the corresponding historical routing stack is updated:

  • Routes that do not exist in the new list are deleted from the historical Route list
  • The Page in the new list, if it does not already have a Route synchronized to it, is inflate to a Route and is then inserted into the position specified in the history routing stack
  • The order in which the history routes are stacked is updated to ensure that they are the same as the new Pages list

The Transition Delegate determines how the Route enters the exit screen.

For the new Page parameter, Navigator also provides a new onPopPage callback. The Navigator calls this method to push the route specified by Page. If the receiver agrees to the callback, the framework calls the didPop method of the route. If this process succeeds, the framework updates the Navigator with a new Page list that no longer contains the Page that was pushed out, and onPopPage returns true. If the pushed Page is not removed, it is treated as a new Page, and the new Page generates a Route. If the recipient of onPopPage does not want the route to be pushed out, it needs to return the callback false. The onPopPage callback only applies to the topmost Page.

Pageless Routes

Existing imperative apis insert routes through methods such as push, which has nothing to do with Page. In order not to have a disruptive effect on existing functionality, the previous code can continue to run. In the new framework, Pageless routes will be bound under Page routes in the history routing stack. If the position of the Page route in the history stack changes, all Pageless routes bound to it will also move to the new location, and the relative position of the Pageless route will not change. If a Page route is removed from the history routing stack, the Pageless route bound to it is also removed.

The initialRoute parameter that initializes the route list also generates a Pageless route list when Navigator is first inserted into the component tree. The route generated by the initialRoute parameter is placed on top of the route generated by the Page parameter. However, the new framework does not encourage the use of the initialRoute parameter. Under the new framework, it is better to provide a Page parameter and set initialRoute to NULL.

Transition DelegateThe

As pages are added and removed from the Navigator, the Transition delegate determines how the corresponding Route should enter and exit the screen. To do this, the interim agent does two things:

  1. When adding/removing routes, whether routes should appear/disappear animated or appear/disappear directly

  2. When inserting and deleting routes at the same location, how should they be sorted during the transition

For example, if the Navigator’s Page list changes from [A, B] to [A, C], then there are two possibilities in the transition scenario:

  1. C is added (possibly with animation) on top of B, and B is removed (possibly with animation) from below (this removal may be delayed until C’s animation is complete).

  2. C is added below B (possibly with an animation), and B is removed above it to show C

The first visual effect will make the user feel as if C is pressed on TOP of B, while the second will make the user feel as if B is popped up to show C.

For the first effect, the Navigator routing stack is in [A, B, C] order, and for the second effect, the Navigator routing stack is in [A, C, B] order. There is no way to determine which effect is desired simply by comparing the old and new Page lists. Therefore, the transition agent is responsible for determining the order of the pages to achieve the specified effect.

When the Page list changes, the transition agent receives a set of HistoryDiff objects that are responsible for keeping track of each location that is added/removed. HistoryDiff contains an ordered set of Routes (added/removed), and within this framework you can see what Routes are in the history stack before the diff position and what Routes are in the history stack after that position. The role of the transition agent is to determine whether a Route added or removed should be animated in or out. Further, the transition broker returns a new history stack to determine the desired order. The relative order of routes in the add and remove lists must be maintained in the merged list.

The transition broker can also determine how Pageless routes bound to removed routes should leave the screen. To accomplish this, HistoryDiff includes a mapping from “Page Routes” to “the List of Pageless routes owned by that route.” Only routes deleted from the list can have an Entry in the map, because newly added routes cannot have Pageless routes. Transitional agents cannot modify the order of Pageless routes, which are always at the top of the Page Route to which they belong and whose life cycle is associated with its life cycle.

The developer can configure a transition agent in the Navigator. The developer can provide a different agent for each Page to achieve different transitions.

If the developer does not provide a custom proxy, the framework provides a default proxy. The default effect is the first method described above. For each HistoryDiff, it puts all the added routes at the top of the deleted route, and animates only when the top of the historical route stack changes:

  • The route added to the top is added, it will give way to the animation, when the animation is complete, all other routes will be added/removed without animation.

  • If routes are deleted, other routes can be added and removed without animation until animation is developed

Summary

In summary, this document recommends the following changes to the public Navigator API:

  • A new class, Page, was introduced to function as a blueprint for creating routes
  • Add the following properties to Navigator:

List declaratively sets the route history onPopPage lets the Navigator push a custom Page transitionDelegate effect

Router

The Router is a new component that acts as a distributor to open and close pages. It wraps around the Navigator component and configures the Page that should be displayed based on the state of the program. Furthermore, the Router also listens for operating system events and changes the page display of the Navigator.

Applications that use the Router component may manage application state to display content. Instead of using an imperative API to display the new route, you use the handler’s state. The Router registers a listener for application state and reconstructs the Navigator in response to state changes. When the Navigator is reconfigured with the new Page, the Route is regenerated to display on the screen. The process of using the Router is as follows:

How the Router detects and responds to changes in application state can be configured using the routerDelegate. Consumers of the Router component need to customize the implementation of the proxy to meet their needs. Developers can also have agents listen for application state changes, but this is not required.

The Router can also help developers listen for operating system-specific events. The Router supports the following events:

  • Initializes the route the first time the program starts
  • Listen for the intent of the operating system to open a new route
  • Listen for a request by the operating system to close the last route in the routing stack

The Router listens for events primarily through the routeNameProvider agent and the backButtonDispatcher agent. The routeNameParser agent parses named routes. RouteNameParser parses the names provided by the routeNameProvider into route data T, which is the route template. The framework provides default implementations of these proxies, and these are generally enough for us. By default, T is the RouteSetting array.

The route data parsed by routeNameParser and the return button notification from backButtonDispatcher are passed to the routerDelegate, The routerDelegate might rebuild the Navigator with the new Page. In the algorithm above, the routerDelegate reconfigures the App state using notifications and reconstructs the Navigator to respond to the state change.

The following algorithm shows the flow of information for the agent:

The part of the routerDelegate that communicates with the application state is optional, depending on the implementation of the routerDelegate provided by the developer. The location and manner in which the backButtonDispatcher and routeNameProvider listen for events (not necessarily the operating system) can be customized through custom agents.

Route Name Provider

The routeNameProvider agent determines how the Router gets the route it wants to display. It is ValueListenable of a String, and its value is to initialize the route when the Router is first built. When the value changes, the Router is notified and changes the Navigator’s configuration, providing the Navigator with a new set of pages.

The string obtained from ValueListenable is parsed by the routeNameParser agent into the specified T, and the parsed data is passed to the routerDelegate, which may rebuild the Navigator.

Is the default routeNameProvider ValueNotifier, parcel Window. DefaultRouteName as the initial value and listening WidgetsBindingObserver. DidPushRoute. RouteNameProvider listeners are notified when didPushRoute fires.

The default routeNameProvider satisfies most scenarios and requires very little customization.

Route Name Parser

The routeNameParser agent gets the String of the current route from the routeNameProvider and converts the String to T. The Transformed data is used by the routerDelegate to configure the Navigator’s route display.

The default routeNameParser parses strings into a set of RouteSettings, which represent pages that are pushed into the Navigator. Default proxy definition structure: /foo/bar? Id =20&name=mike, this string will be parsed into three RouteSetting routes /, /foo, and /foo/bar, and each RouteSetting will have parameters {‘ ID ‘: ’20’, ‘name’: ‘Mike ‘} associated with it.

The default routeNameParser is suitable for most scenarios and requires little customization.

Router Delegate

The Router proxy is the core of the entire router and is responsible for building the correct Navigator. The proxy itself is a Listenable to the Router’s subscription, and the Navigator is rebuilt whenever the broker’s information is changed.

Instead of providing a default implementation of the Router proxy, developers need to implement custom behavior in response to status and events.

RouterDelegate also responds to system events:

  • BackButtonDispatcher is notified when the system back button is pressed and popRoute is called

  • SetInitialRoutePath is called after the Router is built. By default, this method simply calls setNewRoutePath.

  • SetNewRoutePath is called when the routeNameProvider is notified. The name is parsed by routeNameParser.

The Router makes no assumptions about how the routerDelegate will handle these notifications. Possible options include:

  • Ignore these events and do nothing about them

  • Configure the application state to reflect the changes requested by these notifications, and then ask the Router to rebuild with the reconfigured Navigator

  • Build the Router directly with the new Navigator

Back Button Dispatcher

The backButtonDispatcher agent notifies the Router that the user clicked the system’s back key and returns to the previous route. BackButtonDispatcher only works with physical return keys.

BackButtonDispatcher is an implementation of Listenable to which the Router subscribs. The backButtonDispatcher notifies its listener when the Navigator should pop. Of course, this is only for physical return keys.

Instead of notifies its own Router listener, the Dispatcher can notify a child’s backButtonDispatcher and let that child’s Router listener handle the press of the back button. This feature works with nested tabs. There is no limit to the level of nesting in the design, and the child backButtonDispatcher can also have another child node.

There are two concrete implementations of backButtonDispatcher in the framework: the default implementation for the root Router and the nested implementation.

Aimed at the root of the Router. The default implementation simply listening WidgetsBindingObserver didPopRoute, to decide whether to hit the return key. If one has a higher priority than the root dispatcher, its handling is to either notify the listener or pass it on to the child backButtonDispatcher. If multiple byte points are requested for priority, the child of the last requested node is notified. When a child node decides that it no longer wants priority, its predecessors are notified. If no child node declares priority, the notification is distributed to the parent node.

ChildBackButtonDispatcher itself will not listen any system events, it just get events from the parent node. To declare priority, it needs to hold a reference to the parent node. To get references, Routers are designed as inheritedWidgets. The Router component holds backButtonDispatcher reference, this reference is ChildBackButtonDispatcher parent node.

Example Usage

Using the new Navigator API to use the Router, developers basically just need to implement a custom RouterDelegate. For other agents, the default is sufficient. This section demonstrates how the Router and Navigator apis are used.

For this example we assume that the stocks app has three screens:

  • The home page displays a list of favorite stocks and a search icon. Click the list to enter the details page, click the search icon to enter the search page.
  • The details page shows the details of the stock you are interested in, and the back button on the page is clicked to return to the previous page.
  • The search page has a search bar, a back button, and a search result. Click the results to go to the details page. Click the back button to go to the previous page.

The state of the application is a ChangeNotifier that allows the RouterDelegate to listen for changes.

class StockAppState extends ChangeNotifier {
  // If non-null: show the search page with this initial query.
  String get searchQuery;
  String _searchQuery;
  set searchQuery(String value) {
    if (value == _searchQuery) {
      return;
    }
    _searchQuery = value;
    notifyListeners();
  }


  // If non-null: Show the details page for this symbol.
  String get stockSymbol;
  String _stockSymbol;
  set stockSymbol(String value) {
    if (value == _stockSymbol) {
      return;
    }
    _stockSymbol = value;
    notifyListeners();
  }


  // Show these symbols on the home screen.
  final List<String> favoriteStockSymbols; // Loaded from e.g. a database.
}
Copy the code

The state of the application can be used by the RouterDelegate, which needs to be passed to the Router. Other agents can use the default.

class StockAppRouteDelegate extends RouterDelegate<List<RouteSettings>>
    with PopNavigatorRouterDelegateMixin {
  StockAppRouteDelegate(this.state) {
    state.addListener(notifyListeners);
  }


  void dispose() {
    state.removeListener(notifyListeners);
  }


  final StockAppState state;


  @override // From PopNavigatorRouterDelegateMixin.
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();


  @override
  void setNewRoutePath(List<RouteSettings> configuration) {
    if(configuration.length ! =1|| configuration.single.name ! ='/') {
      // Don't do anything if the route is invalid.
      return;
    }
    // Update state; if this modifies the state it will call our listener,
    // which will cause a rebuild.
    state.searchQuery = configuration.single.arguments['searchQuery'];
    state.stockSymbol = configuration.single.arguments['stockSymbol'];
  }


  @override
  Widget build(BuildContext context) {
    // Return a Navigator with a list of Pages representing the current app
    // state.
    return Navigator(
      key: navigatorKey,
      onPopPage: _handlePopPage,
      pages: [
        MaterialPageBuilder(
          key: ValueKey<String> ('home'),
          builder: (context) => HomePageWidget(),
        ),
        if(state.searchQuery ! =null)
          MaterialPageBuilder(
            key: ValueKey<String> ('search'),
            builder: (context) => SearchPageWidget(),
          ),
        if(state.stockSymbol ! =null)
          MaterialPageBuilder(
            key: ValueKey<String> ('details'),
            builder: (context) => DetailsPageWidget(),
          ),
      ],
    );
  }


  bool _handlePopPage(Route<dynamic> route, dynamic result) {
    Page page = route.settings;
    if (page.key == ValueKey<String> ('home')) {
      assert(! route.willHandlePopInternally);// Do not pop the home page. 
      return false;
    }


    final bool result = route.didPop(result);
    assert(result);
    // Update state to remove the page in question; if this modifies the state
    // it will call our listener, which will cause a rebuild.
    if (page.key == ValueKey<String> ('search')) {
      state.searchQuery = null;
      return true;
    }
    if (page.key == ValueKey<String> ('details')) {
      state.stockSymbol = null;
      return true;
    }
    assert(false); // We should never be asked to pop anything else.
    return true; }}Copy the code

Coexistence of imperative and declarative API

As mentioned above, the following explains imperative and declarative apis. Medium – and large-sized projects can choose to configure the Navigator’s history routing stack via the Router, and only use imperative apis to launch very ephemeral routes, such as Dialog and Alert. A flexible Router may be more useful later because it will be easier to integrate and support new features, such as linkability and saving/restoring the current instance state (for example, when the operating system terminates an application in the background due to low memory) into the framework.

Detailed Design

This section covers some design details.

Navigator

This section describes how the Page is implemented, how the Navigator tracks route status, and how the Navigator synchronizes updates with the Page array.

Pages

The Route already has a RouteSetting property, and the Page is basically the RouteSetting, because the Page can also describe the configuration of the Route. Therefore, it makes sense to design Page as a subclass of RouteSetting.

Each Page has an optional Key property, just like the Key of a Widget, which is used as a flag bit for updates. LocalKey is useful because Page only operates in a one-dimensional list.

To do this, Page must implement the createRoute method. The entry to createRoute is BuildContext and returns a page-specific Route. This method is called when the Page is first added to the Navigator’s historical routing stack. The Route returned by this method must have a Settings property, which is Page.

Finally, Page implements the canUpdate method. This method, like Widget’s canUpdate method, is implemented by default to return true if the new Page and the new Page have the same type and key. Method is called when the Navigator is given a new Page list.

To sum up:

abstract class Page<T> extends RouteSettings {
  const Page({
    this.key,
    String name,
    Object arguments,
  }) : super(name: name, arguments: arguments);


  final LocalKey key;


  bool canUpdate(Page<dynamic> other) {
    return other.runtimeType == runtimeType && other.key == key;
  }


  Route<T> createRoute(BuildContext context);
}  
Copy the code

Route State Machine

Both imperative and declarative apis transition the state of a Route from one state to the next, triggering notifications of state changes. For example, when the Navigator is asked to pop a Route, it calls route.didpop () to trigger the Route exit transition and waits for the animation to complete before releasing the Route. For Navigator, the Pop request will transition the Route state from idle to “waiting for Pop to complete” to disposed. These lifecycle state changes are currently coded implicitly with imperative apis.

Imperative apis must implement the same transitions as declarative apis. To avoid duplicate coding, lifecycle transitions are extracted into shared methods, and both apis simply mark the transitions of the route before calling the core method. The core method is flushHistoryUpdates, which performs the actual transitions and triggers all transition effects.

The following algorithm describes the lifecycle transformation of a Route. The state marked with * is instantaneous and is the marker state that tells the flushHistoryUpdates route what transitions need to be performed next. The # flag indicates that Route will stay in the Route state until the next event.

The following events determine the Route lifecycle:

  • The Route generated by parsing the initial argument is added to the history routing stack in the Add * state

  • The Route added by the push() method is in the push* state, and the Route removed by the pushAndRemoveUntil() method is in the remove* state

  • The Route added by the pushReplacement() method starts with pushReplace* and, when replaced, will be in the remove* state

  • The replace() method adds routes that start in the replace* state and are replaced as remove*

  • The pop() method makes the top-level Route pop*

  • Routes added to the Navigator through the Page array may start in the push*, replace*, or Add * state, depending on the transition proxy

  • Remove the Navigator’s route through the Page array, which may be POP *, complete*, or remove*, at the discretion of the transition broker

  • After the push animation is complete, start from the pushing# state and go down

  • When finalizeRoute is called to indicate that the pop animation is complete, proceed from popping#

  • When all the push of the Route is complete, or the top Route is idle, the Route continues down from removing# and adding# to avoid visible visual failures.

Notifying routes that their new previous/next Route (via didChangeNext() or didChangePrevious()) is delayed until all transient changes indicated by an asterisk are processed. This avoids notifying the Route of the transient state that is about to disappear. The Route is also only informed of the next active Route.

What is not shown is the case of removing the edge of Route (which is in the push # state) while the push animation is still running. This is also fully supported: a route in push# can skip idle and enter one of the states directly after idle.

The Route object and its state are bound to the RouteEntry object. The Navigator’s historical routing stack is simply the RouteEntries array.

Updating Pages

When the Page array is changed, the history routing stack is updated. In this case, the Navigator constructs a new historical routing stack that matches the pages array. If canUpdate returns true, the new Page matches the old Route. By default, the comparison type is Runtime and key. If the Page skin matches the Route, the Route RouteSetting is updated with the new Page. The RouteEntry and Pageless routes of routes are copied to the new historical routing stack

If there are no matching pages in the old list, the Page calls createRoute. The newly generated Route is wrapped in RouteEntry and added to the new history routing stack

This procedure iterates through the Page array. Finally, routes without pairs of pages are marked removed and will be reused when new pages are included in the list. Routes held in the historical routing stack can be triggered and wait to be released

Routes after the idle state can no longer match the Page because they are basically exiting

The transition agent determines the order in which routes are added and removed, and also how routes transition in and out

Transition Delegate

From a mechanical point of view, the transition broker determines the state of the Route that is added to the history routing stack. The state diagrams above are: Push *, replace*, and Add *. Similarly, the state after idle is determined by the transition agent. The idle states are pop*, remove*, and complete*.

As mentioned above, the merged history routing stack is split into multiple HistoryDiffs that are handed over to the transition broker one by one. The following example shows how the difference is created, with lowercase letters for Pageless routes, uppercase letters for Page routes, and PA for the corresponding Page of route A:

Two HistoryDiffs are generated, the first containing the following information:

The transition proxy takes these diff’s and calls the methods E and F, marking them as one of the states of push*, replace*, or Add *. Similarly, B, C, and the associated Pageless routes X, Y, and Z are called by one of the pop*, remove*, and complete* methods. Finally, a new list is returned that incorporates the added and removed routes, resulting in [E, B, F, C]. The second HistoryDiff is the following:

The transition agent handles Diff similarly, since “Routes after Diff “is empty, the transition agent can infer that HistoryDiff is at the top of the history stack. Adding and removing lists in HistoryDiff and routes included in maps are basically read-only views of RouteEntries, which Navigato uses internally to manage the history stack. Read-only views give transition agents access to the state of the Route and RouteSettings. There are also methods exposed to change the state of RouteEntry. Other RouteEntries cannot access additional methods.

Routes & RouteSettings

Now, if you want to add a Route to the history routing stack without animation, you do it as an initial Route. Declarative apis are simple to implement. This scenario is especially meaningful when routes are blocked. To support this, the didAdd method is added. The didAdd method is unanimated in contrast to didPush, which is also used to initialize the route. InitialRoute is already useless for this one change.

Router

The API between the Router and its various delegates is completely asynchronous. The routeNameProvider notifies the Router asynchronously when a new Route is available. RouteNameParser returns a Future that generates a result when parsing is complete. The routerDelegate notifies the Router when a new Navigator configuration is available.

The asynchronous design gives the developer more flexibility: routeNameParser may need to communicate with OEM threads to get more Route data, and the communication is asynchronous, so the parsed API needs to be asynchronous as well. Similarly, design applies to other API methods as well.

When agents can perform their work completely synchronously, SynchronousFuture should be used in their implementation. This will allow the Router to proceed in a fully synchronous manner. However, the Router fully supports asynchronous working if needed.

To enable proper asynchronous processing, special care must be taken when implementing the Router: When the Future returned by the agent completes, you need to check whether the request that created the Future is still the current request. If another new request has already been issued, the completion value of the Future should be ignored.

Router Delegate

The routerDelegate must implement the following interface:

abstract class RouterDelegate<T> implements Listenable {  
     void setInitialRoutePath(T configuration); 
     void setNewRoutePath(T configuration);  
     Future<bool> popRoute();  
     Widget build(BuildContext context);
}
Copy the code

The RouterDelegate’s task is to return a properly configured Navigator whose configuration is the Page array that should be displayed on the screen. Whenever the RouterDelegate changes the configuration of the Navigator, the notifyListeners() method should be called. The Router component will rebuild from this signal and request a new Navigator from the RouteDelegate build method.

Other routerDelegate methods are called by the Router in response to system events: SetInitialRoutePath and setNewRoutePath are called when the Route is initialized or the routeNameProvider’s new Route is resolved. In response to these calls, the proxy is passed through notifyListeners, and the Navigator is rebuilt to respond to the notification.

PopRoute () is called when backButtonDispatcher reports that the operating system wants to return. This may cause the RouterDelegate to forward the pop to the previous Navigator, returning true if the RouterDelegate was able to handle the pop, and false otherwise. What happens after returning false depends on the implementation of the backButtonDispatcher

Back Button Dispatcher

As described in the Overview section, the framework will ship with two concrete BackButtonDispatcher implementations (RootBackButtonDispatcher and ChildBackButtonDispatcher), who both implement the following interface: As described earlier, the framework provides two concrete implementations of BackButtonDispatcher. The BackButtonDispatcher interface is as follows:

abstract class BackButtonDispatcher implements Listenable {  
    void takePriority();  
    void deferTo(ChildBackButtonDispatcher child);  
    void forget(ChildBackButtonDispatcher child);
} 
Copy the code

When calling on ChildBackButtonDispatcher takpriority (), it will be on its parent class called deferTo (). The parent node remembers all the children in the sequential list that called the method. When the parent node (or the operating system’s RootBackButtonDispatcher) notifies it that it has pressed the back button, it forwards the notification to the last child node in the list via a method call. If the list is empty, it notifies its Router by calling a notifyListeners() in the parent class. If the subroutine no longer wants to receive the return button notification, it can also call forget() on the parent program. In this case, the parent removes the child from its internal list.

When takPriority () is called on any BackButtonDispatcher, the Dispatcher also clears its internal child list and no longer forwards the back button notification to any child node.

Integration

Navigator has been integrated into the WidgetsApp component for the benefit of users. We hope that the Router will be the best way to interact with Flutter applications

Although both imperative and responsive apis now coexist, the framework layer adds a new named construct for MaterialApp, withRouter, that can set up the agent described above.

The new constructor cannot set the following parameters, which can be implemented in the proxy:

  • navigatorKey
  • initialRoute
  • onGenerateRoute
  • onUnknownRoute
  • navigatorObservers
  • pageRouteBuilder
  • routes