The introduction

In Flutter, page hops between routes use navigator. push and navigator. pop methods.

There are two ways to pass data when a page jumps:

1. The constructor of the target page accepts arguments explicitly. For example, jump to SearchPage and receive a string parameter, as shown below.

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage("Parameters passed")));
Copy the code

2. We can also use the RouteSetting parameters of Route to deliver data. For example,

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SearchPage()
    , settings: RouteSettings(arguments: "Parameters passed")));
Copy the code

The parameters passed to the SearchPage page can be retrieved as follows:

String arg = ModalRoute.of(context).settings.arguments;
Copy the code

Here we want to say a second way of data transfer principle, let us see from the source code!

The source code parsing

Big inference

How exactly do you start parsing the source code? In the form of break points, we can see the execution of each method step by step. Since the final target page is the SearchPage, we can put the breakpoint in the build method of the SearchPage. The breakpoint is in the build method of SearchPageState, as shown below.

context
StatefulElement
_parent

Navigator
Navigator.push
SearchPage
SearchPage
Navigator
Widget
SearchPage
Navigator

Next, we hit the breakpoint in the SearchPage using the modalroute.of method, as shown in the figure below.

ModalRoute
of

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    returnwidget? .route; }Copy the code

The context here is SearchPageElement, calls the inheritFromWidgetOfExactType method, and the _ModalScopeStatus passed as a parameter in the past.

@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if(ancestor ! = null) { assert(ancestor is InheritedElement);return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
 }
Copy the code

[inheritedWidgets] [inheritedWidgets] [modalScopestatus] [InheritedElement] [modalScopestatus] [inheritedWidgets] [modalScopestatus] [InheritedElement] So when is _inheritedWidgets assigned? We did a global search and found that _inheritedWidgets are operated on in _updateInheritance.

void _updateInheritance() { assert(_active); final Map<Type, InheritedElement> incomingWidgets = _parent? ._inheritedWidgets;if(incomingWidgets ! = null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);else
      _inheritedWidgets = HashMap<Type, InheritedElement>();
    _inheritedWidgets[widget.runtimeType] = this;
 }
Copy the code

First, it assigns the _inheritedWidgets of the _parent class to its _inheritedWidgets, and then assigns itself (this) to the current running type of the _inheritedWidgets.

Now let’s go back to the modalroute.of method above.

static ModalRoute<T> of<T extends Object>(BuildContext context) {
    final _ModalScopeStatus widget = context.inheritFromWidgetOfExactType(_ModalScopeStatus);
    returnwidget? .route; }Copy the code

The first line returns the _ModalScopeStatus object. According to the above conclusion, we know that _inheritedWidgets[_ModalScopeStatus] is assigned in _updateInheritance of _ModalScopeStatus.

. That is to say, the context inheritFromWidgetOfExactType actually find _ModalScopeStatus object from down to up, and then of method returns _ModalScopeStatus route variables.

As you can see from the Widget tree from Navigator to SearchPage, _ModalScopeStatus does exist in the Widget tree above, as shown in the figure below.

So there’s reason to make the following big inference:

The navigator. push method generates the Widget tree from Navigator to _ModalScopeStatus to SearchPage from top to bottom, and Navigator stores the routing information in the _ModalScopeStatus Widget. SearchPage searches bottom-up for _ModalScopeStatus and obtains routing information within it.

Test the big inference

First little corollary

To verify this, we start with the Navigator’s build method and step by step see if the Route object is actually passed to the _ModalScopeStatus object.

Widget build(BuildContext context) {
    return Listener(
      onPointerDown: _handlePointerDown,
      onPointerUp: _handlePointerUpOrCancel,
      onPointerCancel: _handlePointerUpOrCancel,
      child: AbsorbPointer(
        absorbing: false, // it's mutated directly by _cancelActivePointers above child: FocusScope( node: focusScopeNode, autofocus: true, child: Overlay( key: _overlayKey, initialEntries: _initialOverlayEntries, ), ), ), ); }Copy the code

According to the build method above and the Widget tree at the interruption point above, the Widget tree of Navigator is Listener->AbsorbPointer->FocusScope->Overlay, and the build method of Overlay is as follows.

Widget build(BuildContext context) {
    final List<Widget> onstageChildren = <Widget>[];
    final List<Widget> offstageChildren = <Widget>[];
    bool onstage = true;
    for (int i = _entries.length - 1; i >= 0; i -= 1) {
      final OverlayEntry entry = _entries[i];
      if (onstage) {
        onstageChildren.add(_OverlayEntry(entry));
        if (entry.opaque)
          onstage = false;
      } else if (entry.maintainState) {
        offstageChildren.add(TickerMode(enabled: false, child: _OverlayEntry(entry))); }}return _Theatre(
      onstage: Stack(
        fit: StackFit.expand,
        children: onstageChildren.reversed.toList(growable: false),
      ),
      offstage: offstageChildren,
    );
 }
Copy the code

Overlay child Widget tree is _Theatre->Stack, children of Stack is onstageChildren list, and child Widget of Stack is _OverlayEntry. The _OverlayEntry build method is as follows.

Widget build(BuildContext context) {
    return widget.entry.builder(context);
}
Copy the code

Navigator
Widget
_OverlayEntry
Widget
_ModalScope

The widget returned by widget.entry.builder(context) is _ModalScope.

Test the first little inference

Let’s verify that widget.entry is actually retrieved from the _enties list of OverlayState, The _enties list is initialized by passing the _initialOverlayEntries parameter through the Overlay in the Navigator’s build method.

child: Overlay(
    key: _overlayKey,
    initialEntries: _initialOverlayEntries,
),
Copy the code

_initialOverlayEntries are initialized in the initState of the Navigator.

void initState() {... omitfor (Route<dynamic> route in _history)
      _initialOverlayEntries.addAll(route.overlayEntries);
}
Copy the code

Here we pass in overlayEntries of the Route object, which is actually MaterialPageRoute, All overlayEntries in the MaterialPageRoute parent OverlayRoute class have been overwritten, so its actual assignment is in the OverlayRoute install method. The install method is called in the Navigator. Push method, so let’s look at the Install method.

void install(OverlayEntry insertionPoint) { assert(_overlayEntries.isEmpty); _overlayEntries.addAll(createOverlayEntries()); navigator.overlay? .insertAll(_overlayEntries, above: insertionPoint); super.install(insertionPoint); }Copy the code

_overlayEntries Adds the execution result of the createOverlayEntries method.

Iterable<OverlayEntry> createOverlayEntries() sync* {
    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
    yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
Copy the code

The above method builds an OverlayEntry object where the Builder argument passes _buildModalScope and _buildModalScope returns the _ModalScope object.

Widget _buildModalScope(BuildContext context) {
    return_modalScopeCache ?? = _ModalScope<T>( key: _scopeKey, route: this, // _ModalScope calls buildTransitions() and buildChild(), defined above ); }Copy the code

At this point, we finally verify the correctness of the first little corollary, that is, the child Widget of _OverlayEntry is _ModalScope, and that _ModalScope also contains routing information, the Route variable.

Verify the correctness of the big inference by the small inference

Let’s look at the build method of _ModalScope.

Widget build(BuildContext context) {
    return_ModalScopeStatus( route: widget.route, ... Omitted); }Copy the code

It returns _ModalScopeStatus and assigns its route variable to the route variable of _ModalScopeStatus, which we said is the MaterialPageRoute object.

That is, once you get the _ModalScopeStatus object, you can get the MaterialPageRoute object using its route method and the RouteSetting object using its setting variable. The Arguments variable of the RouteSetting object is the parameter that the route passes in.

Seeing this, it’s easy to understand why a method like the following can be used in the SearchPage to retrieve the data passed in by the route.

String arg = ModalRoute.of(context).settings.arguments;
Copy the code

Because using modalroute. of(context), SearchPage searches for _ModalScopeStatus from the bottom up, and the _ModalScopeStatus object is found using the route variable. The MaterialPageRoute object can then be accessed by calling the Setting.arguments of the MaterialPageRoute object. This is how the Navigator data is passed.

From the above analysis, we know that an important functional component of Flutter is InheritedWidget, through which we can achieve data sharing across components. For more ways to use inheritedWidgets, refer to Data Sharing