We’ve already covered Overlay, Route, etc., to fully prepare for page Overlay. In this section, we’ll parse the most commonly used piece of code, navigator.push. Compared with Navigator 1, Navigator 2 is more declarative, adding apis such as Page. Later, I will translate the design principles of Navigator 2 specifically. This section only tracks the push process of Navigator and connects the previous key points.

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

Mode of routing operations

Our routing operations are basically divided into three categories: open, close, and replace. The corresponding apis to the Navigator are push, POP, and Replace. Each category is divided into direct and indirect operations according to the operation mode. The direct operation mode is to operate the Route directly, and the indirect operation mode is to operate the Route by name.

The overall API approach is as follows:

Probably the most common apis we use are push and POP. Push and POP are opposite operations, so we’ll just track the push process.

Add the routing

The common way to directly add routes is as follows:

Navigator.push(context, MaterialPageRoute(builder: (context) {
 return const Text("Page or dialog box");
})); 

Navigator.of(context).push(MaterialPageRoute(builder: (context) {
 return const Text("Page or dialog box");
}));

Copy the code

We simply tell Navigator what the next route is, and Navigator begins its display process.

Indirect route adding:

Navigator.pushNamed(context, "Route name");

Copy the code

We tell Navigator what the name of the route is, and Navigator looks up the route corresponding to the name in the pre-registered routing table before Navigator begins its display process.

This indirect approach is more flexible and can add route interception while looking up names.

Let’s follow its display flow in a direct way.

Navigator. Push pushes the route

   Navigator.push(context, route)
Copy the code

Push is a static method in Navigator. It is written like many system components. Inside the method is:

@optionalTypeArgs
staticFuture<T? > push<Textends Object?>(BuildContext context, Route<T> route) {
  return Navigator.of(context).push(route); / / first place
}

static NavigatorState of(
  BuildContext context, {
  bool rootNavigator = false,
}) {
  NavigatorState? navigator;
  if (context is StatefulElement && context.state is NavigatorState) {
    navigator = context.state as NavigatorState;
  }
  if (rootNavigator) { / / in the second place
    navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
  } else {
    navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
  }
  returnnavigator! ; }Copy the code

Navigator.of is a StatefulWidget whose logic is in its State β€”β€”β€”β€” NavigatorState. The of method returns NavigatorState.

The value of rootNavigator indicates whether to return the topmost NavigatorState. If it is false, it means to look up the nearest NavigatorState. If it is true, it means to look up the nearest NavigatorState. Go up to the topmost NavigatorState.

Looking back at the code in the first place, rootNavigator is false, which means just go up to the nearest NavigatorState. For example:

If Navigator. Of (context) is called from G, it returns C, and if Navigator. Of (context, rootNavigator: True) returns A. Similarly, when node B is looking up, node A is returned whether rootNavigator is used or not.

NavigatorState: Push NavigatorState: push NavigatorState: push NavigatorState: push NavigatorState

The push NavigatorState

@optionalTypeArgs Future<T? > push<T extends Object? >(Route<T> route) { _pushEntry(_RouteEntry(route, initialState: _RouteLifecycle.push)); // Return route. Popped; }Copy the code

As with most apis, Route has a wrapping process, wrapping the MaterialPageRoute we passed in as _RouteEntry, and then performing the _pushEntry action is done, so the logic is concentrated in _pushEntry.

Before we get to the rest, let’s talk about the state of the route.

In the push and POP columns, the developer calls the API of the system, and the status is the same as the method name. The specific meanings of the status are as follows:

The state name meaning
add A Route generated by onGenerateInitialRoute or Pages, after which Install is called
adding Wait for the result of top-level routing
push Routes generated by push, after which Install is called
pushReplace The route generated by pushReplace is followed by a call to Install
pushing Wait for the result of top-level routing
replace Routes generated by replace, after which Install is called
idle The route has been stabilized and is displayed on the page
pop To close the route, the next call is didPop
remove Delete the route and next call didReplace/didRemove
popping Wait for finalizeRoute to be called
removing Waiting for the animation to complete removes the page content from the overlay
dispose Release the route now
disposed The route has been released

Let’s go back to the first code,

 _pushEntry(_RouteEntry(route, initialState: _RouteLifecycle.push)); / / first place
Copy the code

Because we called the push method, the constructed _RouteEntry has a push state. Now let’s look at the concrete _pushEntry logic.

 void _pushEntry(_RouteEntry entry) {
    _history.add(entry); / / first place
    _flushHistoryUpdates(); / / in the second place
    _afterNavigation(entry.route); / / the third place
  }
 
Copy the code

Let’s first look at the code in the first place. The member variable _history adds the route wrapper class. Here we briefly introduce _history. The design principle of Navigator 2.0 isto update _history to achieve declarative effect. _history contains the open route wrapper class, and the last element of the _history array is the current route at the top of the stack or the route to be added.

We’re looking at _flushHistoryUpdates in the second place, which is just flushing the data at the top of the stack, and we’ll look at that later.

The third _afterNavigation will cancel some gesture events.

So, as you can see from the name, the logic is concentrated in the second method.

To summarize:

There is a stack to refresh the history route

Now let’s focus on _flushHistoryUpdates.

The above code is basically divided into three parts: variable initialization, call different logic based on the route state, and display the route content.

Variable initialization

Index: indicates the current route index traversed. The first traversed index is the top of the stack (which we just added). Entry: indicates the current route traversed. The first route traversed is the route at the top of the stack. Previous: indicates the previous entry route.

Let’s take an example:

This part is mainly the assignment of variables, remember its meaning can be, let’s look at the specific processing logic.

Different logic is invoked depending on the route state

When we call navigator. push, the state is _routelifecycle.push, so we’re in Entry.handlepush. We’re looking at the logic. This is where the wrapper class comes in. Instead of handlePush methods in the original routing class, the wrapper class acts as a class enhancement, much like the OverlayEntry.

Let’s start with the method inputs:

Parameter names meaning
navigator Object NavigatorState, the shell component that carries the route
previous Front Indicates the front of the route traversed
previousPresent The state of the previousPresent route that is currently traversed must exist compared to previous, which may be remove
isNewFirst Whether to insert the route to the top of the stack

Refer to the figure above:

C routes are pushed in, A and B are already on the page. If B’s state is between ** add and remove**, such as idle, then C’s previousPresent is B, otherwise it is A.

Given this, let’s look at the logic:

  void handlePush({ required NavigatorState navigator, required bool isNewFirst, required Route<dynamic>? previous, required Route<dynamic>? previousPresent }) {
  
   final _RouteLifecycle previousState = currentState;
   
   route._navigator = navigator;
   route.install(); / / first place
   
   if (currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace) {
     final TickerFuture routeFuture = route.didPush(); / / in the second place
     currentState = _RouteLifecycle.pushing;
     routeFuture.whenCompleteOrCancel(() {
       if(currentState == _RouteLifecycle.pushing) { currentState = _RouteLifecycle.idle; navigator._flushHistoryUpdates(); }}); }else {
     route.didReplace(previous);
     currentState = _RouteLifecycle.idle;
   }
   if (isNewFirst) {
     route.didChangeNext(null);
   }
  ///. Omit code
 }
Copy the code

Let’s look at the first code, this is not familiar, is the initialization of the route. Remember what this initialization is? You might want to look at linear initialization. Our Route is the MaterialPageRoute, just look at the code.

Now that we’re ready to display, we know that the wrapper class doesn’t actually do the actual logic, and that the actual push logic is still in Route, so it’s code in the second place that calls Route’s didPush. In the same way, didPush is linear inheritance. DidPush is much simpler than initialization. Let’s see:


Route:
TickerFuture didPush() {
   returnTickerFuture.complete().. then<void> ((void _) {
     if(navigator? .widget.requestFocus ==true) {
       navigator!.focusScopeNode.requestFocus();
     }
   });
 }

 TransitionRoute:
 @override
 TickerFuture didPush() {
   super.didPush();
   return_controller! .forward(); } ModalRoute:@override
 TickerFuture didPush() {
   if(_scopeKey.currentState ! =null&& navigator! .widget.requestFocus) { navigator! .focusScopeNode.setFirstFocus(_scopeKey.currentState! .focusScopeNode); }return super.didPush();
 }

Copy the code

We saw didPush do two things: focus control and animation drive. One thing to note here is that this animation is the animation progression of buildTransitions in ModalRoute at initialization. BuildTransitions will be called whenever the _Controller animation progression changes.

In turn, we set the state of the Route to _routelifecyce.pushing after the didPush execution in the second section above.

This is the specified flow of the handlePush wrapper class: the initialization of the Route, the driving animation, and the setting of the state of the Route to pushing.

According to the routing

Displaying the routing code is simple by adding the initial OverlayEntry to the Overlay.

The method of adding Flutter is different from that of adding which we introduced earlier — Overlay starts with The Navigator, but reorganizes the content. _allRouteOverlayEntries This variable holds overlayentries for all historical routes. Note that each route contains two overlayentries.

  void rearrange(可迭代<OverlayEntry> newEntries, { OverlayEntry? below, OverlayEntry? above }) {
   final List<OverlayEntry> newEntriesList = newEntries is List<OverlayEntry> ? newEntries : newEntries.toList(growable: false);
   if (newEntriesList.isEmpty)
     return;
   if (listEquals(_entries, newEntriesList))
     return;
   final LinkedHashSet<OverlayEntry> old = LinkedHashSet<OverlayEntry>.from(_entries);
   for (final OverlayEntry entry innewEntriesList) { entry._overlay ?? =this;
   }
   setState(() {
     _entries.clear();
     _entries.addAll(newEntriesList); 
     old.removeAll(newEntriesList);
     _entries.insertAll(_insertionIndex(below, above), old);/ / first place
   });
 }
Copy the code

Focus on the first one, which calls the INSERT process we talked about earlier. So now you can see that Navigator’s route management is just adding the OverlayEntry of the route to the Overlay of Navigator. How does the animation work? We simply added an animation component to our page in response to the animation driver, a route display hierarchy as follows:

conclusion

The page display of Navigator is called Overlay display. We can also develop a simple version of Overlay ourselves. The difference is that Navigator adds excessive animation and focus control to the page display. In addition, I don’t know why there are two overlayentries in the route