Navigator 2.0 provides a declarative API as a new generation of routing that is more in line with the Flutter style. Navigator 2.0 is forward compatible, has a few new apis, and uses a much different approach than Navigator 1.0.

In this article, I’ll break down the underlying logic of Navigator 2.0 to give you an in-depth understanding of it so you can use it more easily.

Context for Navigator 2.0

The official Flutter team modified the route for several reasons:

  1. The Navigator 1.0Only a few are providedpush().pushNamed()andpop()And so on. It is difficult to push or pop up multiple pages, and it is even more difficult to remove and exchange intermediate pages in the stack.
  2. With the advent of Platform support, Flutter has emerged in new usage scenarios, such as web pages changing URL addresses, which require new apis to support.
  3. Navigator 2.0 addresses the requirements scenario for nested scenarios, making it more flexible and easy for developers to use.
  4. The Navigator 2.0Provides aThat typetheAPITo solve the previous routingImperative programmingThe way to make the programming style unified.

The Navigator 2.0 APIS are more numerous, but the logic is still clear. Let’s introduce them one by one.

Page

Page represents the immutable configuration information of the Page. It represents a Page. The Widget configuration information is converted to an Element, and the Page configuration information is converted to a Route.

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


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

  @factory
  Route<T> createRoute(BuildContext context);
}
Copy the code
  1. createRouteIs to convert toRouteThe method;
  2. canUpdateThe implementation method andWidgetIs also used fordiffAlgorithm.

RouteSettings

Page’s parent class RouteSettings only holds the name and arguments values.

const RouteSettings({
    this.name,
    this.arguments,
});
Copy the code

Route

A Route represents a page that is actually managed in the Navigator stack.

abstract class Route<T> {
    
    // 1   
    RouteSettings get settings => _settings;
    NavigatorState? get navigator => _navigator;

    // 2
    List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
    
    // 3
    void install() {}
    TickerFuture didPush() {}
    ...
    
}
Copy the code
  1. RouteThe configuration object is heldpageAnd administer itnavigatorObject;
  2. The Route also holds an array of OverlayEntries, and the Overlayentries sit on some kind of Stack Overlay, so the page that we’re writing is sitting on an OverlayEntry;
  3. RouteIt also defines some protocol methods that need to be overridden by subclassesrouteThe callback function received after the state of_RouteEntry.
methods Call time
install Be insertednavigator
didPush Animation goes into display
didAdd Directly display
didReplace Replace the oldroute
didPop requestpoppage
didComplete popAfter the completion of the
didPopNext The currentrouteAt the back of therouteIs the pop
didChangeNext The currentrouteAt the back of therouteBe replaced
didChangePrevious The currentrouteIn front ofrouteBe replaced
changedInternalState The currentroutethestateAfter the change
changedExternalState The currentroutethenavigatorAfter the change

MaterialPage_PageBasedMaterialPageRoute

We can either directly use the Page class that the system provides us, or we can customize a class that inherits from Page. Let’s look at the logic of the MaterialPage provided to us by the official.

MaterialPage Route is _PageBasedMaterialPageRoute class, its inheritance logic is: _PageBasedMaterialPageRoute – > PageRoute – > ModalRoute – > TransitionRoute – > OverlayRoute + LocalHistoryRoute – > the Route.

LocalHistoryRoute

LocalHistoryRoute can add some LocalHistoryEntry to the Route. When LocalHistoryEntry is not empty, the didPop method is going to remove the last LocalHistoryEntry when it’s called, otherwise the Route is going to get popped.

OverlayRoute

OverlayRoute basically holds an Array of OverlayEntry routes that subclasses assign when they are inserted into the Navigator.

abstract class OverlayRoute<T> extends Route<T> { @factory Iterable<OverlayEntry> createOverlayEntries(); List<OverlayEntry> get overlayEntries => _overlayEntries; void install() { _overlayEntries.addAll(createOverlayEntries()); super.install(); }}Copy the code
TransitionRoute

The TransitionRoute is mainly responsible for the animation part.

abstract class TransitionRoute<T> extends OverlayRoute<T> { Animation<double>? get animation => _animation; Animation<double>? get secondaryAnimation => _secondaryAnimation; void install() { _animation = createAnimation() .. addStatusListener(_handleStatusChanged); super.install(); } TickerFuture didPush() { super.didPush(); return _controller! .forward(); } void didAdd() { super.didAdd(); _controller! .value = _controller! .upperBound; } bool didPop(T? result) { _controller! .reverse(); return super.didPop(result); } void didPopNext(Route<dynamic> nextRoute) { _updateSecondaryAnimation(nextRoute); super.didPopNext(nextRoute); } void didChangeNext(Route<dynamic>? nextRoute) { _updateSecondaryAnimation(nextRoute); super.didChangeNext(nextRoute); }}Copy the code
  1. TransitionRouteThere are_animationandsecondaryAnimationOf the two animations, the former is responsible for the currentRoutethepushandpopAnimation, the latter responsible for the nextRouteforpushandpopThe time itselfRouteThe animation.
  2. _animationisinstallSo it’s going to be,secondaryAnimationWell, most of the time it’s nextRoutethe_animation, sodidPopNextanddidChangeNextNeeds to be updated whensecondaryAnimation.
  3. If you don’t need to animate it is calleddidAddMethod,RouteIt’s the method that’s called passively, it’s actually_RouteEntryAccording to the (The Navigator to determine theThe method called by the state determination.
ModalRoute

The main function of ModalRoute is to prevent the Route except the top layer of the Route to user interaction, which is also very rich knowledge.

abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> { Iterable<OverlayEntry> createOverlayEntries() sync* { yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier); yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState); }}Copy the code
  1. ModalRouteIt produces two very important onesOverlayEntry_modalBarrierand_modalScope.
  2. _modalBarrierImplemented to block user to top layerRouteIn addition to theRouteFunctions for user interaction;
  3. _modalScopeWill holdrouterTheir own,_modalScopeThis is called at build timerouterthebuildTransitionsandbuildChildMethod, parameterroutertheanimationandsecondaryAnimation, that is,TransitionRouteTwo animation properties in;
Widget _buildModalScope(BuildContext context) { return _modalScopeCache ?? = Semantics(sortKey: const OrdinalSortKey(0.0), child: _ModalScope<T>(key: _scopeKey, route: this, // _ModalScope calls buildTransitions() and buildChild(), defined above ) ); } Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation); Widget buildTransitions( BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child, ) { return child; }Copy the code

Let’s look at the contents of _ModalScope’s _ModalScopeState:

class _ModalScopeState<T> extends State<_ModalScope<T>> { late Listenable _listenable; final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope'); void initState() { super.initState(); final List<Listenable> animations = <Listenable>[ if (widget.route.animation != null) widget.route.animation!, if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation!, ]; _listenable = Listenable.merge(animations); if (widget.route.isCurrent) { widget.route.navigator! .focusScopeNode.setFirstFocus(focusScopeNode); }}}Copy the code
  1. _listenableisroutetheanimationandsecondaryAnimationThe combination of;
  2. focusScopeNodeIs the focus, when initializing itnavigatorIs set to this focus, thus implementing the topmostRouteBefore getting focus, shield against othersRouteFocus acquisition;
Widget build(BuildContext context) { // 1 RestorationScope return AnimatedBuilder( animation: widget.route.restorationScopeId, builder: (BuildContext context, Widget? child) { return RestorationScope( restorationId: widget.route.restorationScopeId.value, child: child! ,); }, // 2 _ModalScopeStatus child: _ModalScopeStatus( route: widget.route, isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates canPop: widget.route.canPop, // _routeSetState is called if this updates child: Offstage( offstage: widget.route.offstage, // _routeSetState is called if this updates child: PageStorage( bucket: widget.route._storageBucket, // immutable child: Builder( builder: (BuildContext context) { return Actions( actions: <Type, Action<Intent>>{ DismissIntent: _DismissModalAction(context), }, child: PrimaryScrollController( controller: primaryScrollController, child: FocusScope( node: focusScopeNode, // immutable // 3 RepaintBoundary child: RepaintBoundary( // 4. AnimatedBuilder child: AnimatedBuilder( animation: _listenable, // immutable builder: (BuildContext context, Widget? child) { // 5. buildTransitions return widget.route.buildTransitions( context, widget.route.animation! , widget.route.secondaryAnimation! , AnimatedBuilder( animation: widget.route.navigator? .userGestureInProgressNotifier ?? ValueNotifier<bool>(false), builder: (BuildContext context, Widget? child) { final bool ignoreEvents = _shouldIgnoreFocusRequest; focusScopeNode.canRequestFocus = ! ignoreEvents; return IgnorePointer( ignoring: ignoreEvents, child: child, ); }, child: child, ), ); }, child: _page ?? = RepaintBoundary( key: widget.route._subtreeKey, // immutable child: Builder( builder: (BuildContext context) { return widget.route.buildPage( context, widget.route.animation! , widget.route.secondaryAnimation! ,); },))))))))); },),),),); }Copy the code

The build method of modalScope Estate is a very clever method:

  1. RestorationScopeTo be responsible for theRouteFunction used for data recovery;
  2. _ModalScopeStatusisInheritedWidgetIt stays rightRouteSo we’re callingModalRoute.of(contex)This is what you get when you get a page pass parameter_ModalScopeStatus, and find the corresponding pass parameter.
  3. I put one in the middleRepaintBoundaryYou can limit the area to redraw, so that you can improve the efficiency of animation when drawing;
  4. At the bottom of theAnimatedBuilderthisWidgetIt’s the core, thisAnimatedBuilderthechildIs made up ofroute.buildPage()What this method creates is actually usPagethechild“, the content of the page written by the developer; thisAnimatedBuilderthebuilderMethod is calledroute.buildTransitions(), which drives the animation is_listenableThat is to sayanimationandsecondaryAnimationCan drive its animation process. It makes sense: the presentRoutethepopandpushAnd nextRoutethepopandpushWill trigger an animation.
PageRoute

The main PageRoute is to make the Route at the bottom of the top layer invisible by clicking _modalBarrier to keep the current Route from popping up in the Navigator stack.

abstract class PageRoute<T> extends ModalRoute<T> {

  @override
  bool get opaque => true;

  @override
  bool get barrierDismissible => false;

}
Copy the code
_PageBasedMaterialPageRoute

_PageBasedMaterialPageRoute is fu wrote buildPage method, returns the developers write the interface;

class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> { Widget buildContent(BuildContext context) { return _page.child; }}Copy the code

Official offers a default pop and push the movie, they are mixed with the MaterialRouteTransitionMixin. MaterialRouteTransitionMixin will according to different platforms have different implementations, iOS is about animation, Android is the animation, web is about animation.

In iOS, for example, we end up using a method called CupertinoPageTransition:

SlideTransition(
    position: _secondaryPositionAnimation,
    textDirection: textDirection,
    transformHitTests: false,
    child: SlideTransition(
    position: _primaryPositionAnimation,
    textDirection: textDirection,
    child: DecoratedBoxTransition(
        decoration: _primaryShadowAnimation,
        child: child,
    ),
)
Copy the code

Confused to see a SlideTransition nested on a child? Two animations on one Widget?

Let’s explain the other parameters:

  1. textDirectionDetermines how to slide, because some languages sort from right to left;
  2. transformHitTestsSet to flase, the response position of the click event is not affected by the animation;
  3. _primaryShadowAnimationIs to set a shadow in an animation.

_secondaryPositionAnimation from Offset. The zero to Offset (0.0) – 1.0/3.0, and under normal circumstances is moving from right to left a third of the screen width.

Final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(begin: Offset. Zero, end: const Offset(-1.0/3.0, 0.0),);Copy the code

The _primaryPositionAnimation is from Offset(1.0, 0.0) to offset.zero, which normally means moving from the invisible right to the far left of the screen and taking up the entire screen width.

Final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(begin: const Offset(1.0, 0.0), end: Offset. Zero,);Copy the code

< span style = “max-width: 100%; clear: 100%; clear: 100%

  1. The new addedRouteIs be_primaryPositionAnimationDirectly driven, that is, executed from right to left_kRightMiddleTweenAnimation;
  2. _secondaryPositionAnimationIt’s just changed the value that we had beforeTransitionRouteAs mentioned in the introduction of the newRoutetheanimationIt’s assigned to the previous oneRoutethesecondaryAnimationProperties._ModalScopeStateIntroduced insecondaryAnimationIt is available to driveRouteThe animation, that is to say the previous oneRouteI can also produce one_kMiddleLeftTweenAnimation;

Summary:

The animation driver moves the newly added Route from the right side of the screen to the left. The animation driver assigns the value to the secondaryAnimation driver of the previous Route to move the previous Route 1/3 screen positions to the left.

The logic of push is similar, only a reverse animation. The previous Route moves 1/3 screen width to the right under the secondaryAnimation driver, and the current Route moves off the screen under the animation driver.

To see the animation Slow down, click on the Flutter DevTools Slow Animations:

Stage summary

_RouteEntry

Navigator is not the Route that you operate on directly, but the wrapper class _RouteEntry that encapsulates the Route.

_RouteEntry(
    this.route, 
    {
      required _RouteLifecycle initialState,
      this.restorationInformation,
    })
Copy the code

_RouteEntry holds a _RouteLifecycle, the state of the route, in addition to the route.

Function mainly is a function of modified _RouteLifecycle state, such as markForPush, markForAdd, markForPop, markForRemove, markForComplete, etc. After plus _RouteLifecycle is marked on the Route operation function, such as handlePush, handleAdd, handlePop, remove, and so on.

Navigator

Navigator({
    Key? key,
    this.pages = const <Page<dynamic>>[],
    // ...
})
Copy the code

The Navigator constructor has a key attribute, Pages, which the Navigator converts to the _RouteEntry array for Routes.

The logic for implementing declarative programming is to modify the contents of the Pages, and the Navigator automatically jumps, returns, replaces, and so on. The Navigator. Push, the Navigator. Pop etc method has been used in previous will not need to consider the use of the developers.

Let’s look at the important NavigatorState code.

class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
    
    List<_RouteEntry> _history = <_RouteEntry>[];
    
    late GlobalKey<OverlayState> _overlayKey;
    OverlayState? get overlay => _overlayKey.currentState;
    
    final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
    
}
Copy the code
  1. _historyispagesIn eachPagethroughcreateRouteThe generated_RouteEntryThe array;
  2. OverlayStateoverlayWhat it represents isOverLay, which is responsible for placing eachRoutetheoverlayEntriesThe array;OverLayIt’s equivalent to aStack, specifically for placementOverlayEntry.

NavigatorState’s core method is the didUpdateWidget method, which calls an _updatePages() method:

void didUpdateWidget(Navigator oldWidget) {
    _updatePages();
}
Copy the code

The _updatePages method diff the pages, updates the _RouteLifecycle for each _routeEntry in the _history array, and finally calls the _flushHistoryUpdates() method.

_routeEntry comparison method and MultiChildRenderObjectElement comparison method is the same, earlier than back can reuse elements, and then from the back forward than can reuse elements, then for the rest of the elements for reuse or new, and can’t reuse elements for destruction.

void _flushHistoryUpdates({bool rearrangeOverlay = true}) { final List<_RouteEntry> toBeDisposed = <_RouteEntry>[]; while (index >= 0) { switch (entry! .currentState) { case _RouteLifecycle.push: case _RouteLifecycle.pushReplace: case _RouteLifecycle.replace: entry.handlePush( navigator: this, previous: previous? .route, previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)? .route, isNewFirst: next == null, ); if (entry.currentState == _RouteLifecycle.idle) { continue; } break; / /... } index -= 1; next = entry; entry = previous; previous = index > 0 ? _history[index - 1] : null; } _flushObserverNotifications(); _flushRouteAnnouncement(); for (final _RouteEntry entry in toBeDisposed) { for (final OverlayEntry overlayEntry in entry.route.overlayEntries) overlayEntry.remove(); entry.dispose(); } if (rearrangeOverlay) { overlay? .rearrange(_allRouteOverlayEntries); }}Copy the code
  1. According to each_RouteEntrythe_RouteLifecycleCall the corresponding method, for example ifRouteIs marked as_RouteLifecycle.pushThe callhandlePushMethod, so thisRouteIs calledinstallMethods insertNavigatorTree, and then animate it;
  2. _flushObserverNotificationsIs for each_NavigatorObservationThe listener notifies;
  3. _flushRouteAnnouncementMostly for eachRouteTo sort out and update the context of,secondaryAnimationThis is when the update of;
  4. Will not be needed_RouteEntrytheoverlayEntriesfromOverlayRemoved from, because no longer needed to display;
  5. And then you take all of the_RouteEntrytheoverlayEntriesUpdate to theOverlayOn, the code isbuildMethod you can see the logic added as follows.
Widget build(BuildContext context) {
    return HeroControllerScope.none(
      child: 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: UnmanagedRestorationScope(
              bucket: bucket,
              child: Overlay(
                key: _overlayKey,
                initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
              ),
            ),
          ),
        ),
      ),
    );
  }
Copy the code

The HeroControllerScope, by the way, is a Widget that animates a Hero, similar to shared element animation on Android.

Stage summary

So far, we’ve been able to switch routes by switching pages in the Navigator. Is that the end of the article? No, because Navigator 2.0 is developed for the whole platform of Flutter 2.0, there are some issues that are not solved, such as editing browser urls, web return, android physical key return, etc.

Router

Router({
    Key? key,
    this.routeInformationProvider,
    this.routeInformationParser,
    required this.routerDelegate,
    this.backButtonDispatcher,
  })
  
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<T>? routeInformationParser;
final RouterDelegate<T> routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
Copy the code

We see that a Router has four properties, RouteInformationProvider, RouteInformationParser, RouterDelegate, which processes routing information, BackButtonDispatcher returns the distributor of the processing. They work together to realize the function of routing.

RouteInformation

RouteInformation includes the route location and state of the route. The state is the data.

class RouteInformation {

  final String? location;
  final Object? state;
}
Copy the code
RouteInformationProvider

RouterReportsNewRouteInformation RouteInformationProvider only an abstract method, this method is based on RouteInformation for some additional operation.

abstract class RouteInformationProvider extends ValueListenable<RouteInformation? > { void routerReportsNewRouteInformation(RouteInformation routeInformation) {} }Copy the code

System default is PlatformRouteInformationProvider, its routerReportsNewRouteInformation method callback system routing updates, such as the browser will add a History in the History stack access records:

class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {

    void routerReportsNewRouteInformation(RouteInformation routeInformation) {
        SystemNavigator.routeInformationUpdated(
          location: routeInformation.location!,
          state: routeInformation.state,
        );
        _value = routeInformation;
    }

}
Copy the code
RouteInformationParser

This class translates the T page model to the RouteInformation:

abstract class RouteInformationParser<T> {
  
  Future<T> parseRouteInformation(RouteInformation routeInformation);

  RouteInformation? restoreRouteInformation(T configuration) => null;
}
Copy the code

The parseRouteInformation method is used to parse the initial route, for example, to display the startup page based on the RouteInformation(location: “/”);

RestoreRouteInformation is a method that generates the corresponding RouteInformation based on the T page model.

RouterDelegate

A RouterDelegate, as its name suggests, is a class that does the work of the Router. It includes adding a page according to the T-page model, popping a page, and providing built content.

abstract class RouterDelegate<T> extends Listenable {
  
  Future<void> setInitialRoutePath(T configuration) {
    return setNewRoutePath(configuration);
  }

  Future<void> setNewRoutePath(T configuration);

  Future<bool> popRoute();

  T? get currentConfiguration => null;

  Widget build(BuildContext context);
}
Copy the code

Can be mixed with PopNavigatorRouterDelegateMixin popRoute method, you need not oneself to achieve it.

Let’s take a look at how the RouteInformationProvider, RouteInformationParser, and RouterDelegate implement routing initialization from a source code perspective:

class _RouterState<T> extends State<Router<T>> { void initState() { super.initState(); if (widget.routeInformationProvider ! = null) { _processInitialRoute(); } } void _processInitialRoute() { _currentRouteInformationParserTransaction = Object(); _currentRouterDelegateTransaction = Object(); _lastSeenLocation = widget.routeInformationProvider! .value! .location; widget.routeInformationParser! .parseRouteInformation(widget.routeInformationProvider! .value!) .then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget)) .then<void>(widget.routerDelegate.setInitialRoutePath) .then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget)) .then<void>(_rebuild); }}Copy the code

In the _processInitialRoute method we see that the routeInformationParser parses the Value of the routeInformationProvider, The routerDelegate then calls setNewRoutePath to set the route based on the result of this resolution.

routeInformationProvider -> routeInformationParser -> routerDelegate -> (setNewRoutePath)

RouterDelegate override example:

class MyRouterDelegate extends RouterDelegate<PageConfiguration> with ChangeNotifier, PopNavigatorRouterDelegateMixin<PageConfiguration> { final List<Page> _pages = []; final AppState appState; final GlobalKey<NavigatorState> navigatorKey; MyRouterDelegate(this.appState) : navigatorKey = GlobalKey() { appState.addListener(() { notifyListeners(); }); } List<MaterialPage> get pages => List.unmodifiable(_pages); Future<bool> popRoute() { _removePage(_pages.last); return Future.value(false); } Future<void> setNewRoutePath(PageConfiguration configuration) { if (shouldAddPage) { _pages.clear(); addPage(configuration); } return SynchronousFuture(null); } Widget build(BuildContext context) { return Navigator( key: navigatorKey, onPopPage: _onPopPage, pages: buildPages(), ); }}Copy the code
  1. MyRouterDelegateThere are_pagesProperty, which acts asNavigatorthepages;appStateIt’s state management data, using that data to driveMyRouterDelegateThe observer of theta is thetaRouterThat is to refactor, soNavigatorIt will refactor.
  2. popRoutewill_pagesThe last page is deleted, notificationRouterTo refactor, to updateNavigator;
  3. setNewRoutePathto_pagesAdd the correspondingPageTo informRouterNamely to refactorNavigator.

BackButtonDispatcher

BackButtonDispatcher is mainly to solve android, web and other physical return events. It has two subclasses RootBackButtonDispatcher and ChildBackButtonDispatcher can solve the nesting of the Router.

The Return of the BackButtonDispatcher can be handled directly by the RouterDelegate, for example:

class MyBackButtonDispatcher extends RootBackButtonDispatcher { final MyRouterDelegate _routerDelegate; MyBackButtonDispatcher(this._routerDelegate) : super(); // 3 @override Future<bool> didPopRoute() { return _routerDelegate.popRoute(); }}Copy the code
The final summary

conclusion

Navigator 2.0 is much more powerful, and the way it is used has become much smoother. But it has become more complex, which is a huge cost of learning and use, which is why many people think Navigator 2.0 is a failure.

This article mainly analyzes the implementation logic of Navigator 2.0 from the perspective of source code. It should be easy to write the code once the principle is clear.

If you need a Demo, check out the code in the following two articles, especially the first one:

Flutter Navigator 2.0 and Deep Links

Learning Flutter’s new navigation and routing system