setNewRoutePath method

  1. @override
Future<void> setNewRoutePath(
  1. RouteDecoder configuration
)
override

Handles a route reported by the platform (a deep link, or the browser back/forward buttons on Flutter Web).

When the reported route already exists in activePages, the stack is popped back to that entry so the pages above it are removed instead of being duplicated or resurrected. Unknown routes keep the previous behavior and are pushed on top of the stack, preserving deep links.

Implementation

@override
Future<void> setNewRoutePath(RouteDecoder configuration) async {
  final page = configuration.route;
  if (page == null) {
    goToUnknownPage();
    return;
  }
  // Seed the very first route synchronously when no middleware can
  // intervene, so the initial page is part of the router's first frame:
  // a single-pump `tester.pumpWidget(GetMaterialApp(home: ...))` must
  // find the home widget (#3244). Routes guarded by middlewares keep the
  // asynchronous pipeline below, which resolves redirects first.
  if (_activePages.isEmpty &&
      configuration.currentTreeBranch.last.middlewares.isEmpty) {
    _activePages.add(configuration);
    // During the initial route processing the router is amid its own
    // first build and rebuilds right after this call, so notifying is
    // both illegal and unnecessary; the navigator only exists — making
    // a notification required — once that first build completed.
    if (navigatorKey.currentContext != null) notifyListeners();
    return;
  }
  final reportedName = configuration.pageSettings?.name;
  var existingIndex = _activePages.lastIndexWhere(
    (element) => element.pageSettings?.name == reportedName,
  );
  if (existingIndex == _activePages.length - 1 && _activePages.length > 1) {
    // The reported route has the same name as the current top entry. When
    // a lower duplicate exists (duplicates can be pushed with
    // preventDuplicates disabled), the platform is navigating back to that
    // duplicate, so pop to the highest matching entry strictly below the
    // top. Without a lower duplicate this stays a no-op, as the platform
    // is merely re-reporting the current route.
    final lowerIndex = _activePages.lastIndexWhere(
      (element) => element.pageSettings?.name == reportedName,
      _activePages.length - 2,
    );
    if (lowerIndex >= 0) {
      existingIndex = lowerIndex;
    }
  }
  if (existingIndex >= 0) {
    if (existingIndex < _activePages.length - 1) {
      // A platform back navigation with a pageless route (dialog, bottom
      // sheet, imperative [Navigator.push] route) on top must dismiss
      // that overlay instead of popping the page it is anchored to; each
      // back press then closes a single overlay.
      if (_topRouteIsPageless) {
        await handlePopupRoutes();
        notifyListeners();
        return;
      }
      // When exactly one page would be popped, honor the top route's
      // pop-veto surface (PopScope, WillPopScope, Page.canPop), like
      // [popRoute] does. Multi-entry history jumps cannot be vetoed
      // per page.
      if (existingIndex == _activePages.length - 2) {
        final target = _activePages[existingIndex];
        if (await _isPopVetoed()) {
          notifyListeners();
          return;
        }
        // The history may have been mutated while awaiting the veto
        // check (a willPop callback can navigate), mirroring [popRoute]'s
        // post-await guard: re-locate the target entry and bail out when
        // it is gone, so the pop loop cannot remove unrelated pages.
        existingIndex = _activePages.lastIndexWhere(
          (element) => identical(element, target),
        );
        if (existingIndex < 0) {
          notifyListeners();
          return;
        }
      }
    }
    if (existingIndex < _activePages.length - 1) _markNavigationAsPop();
    while (_activePages.length - 1 > existingIndex) {
      _popWithResult();
    }
    notifyListeners();
    return;
  }
  await _push(configuration);
}