Route transition record and transition delegate updates

Summary

We added a new boolean getter isWaitingForExitingDecision to the route transition record and renamed the isEntering getter to isWaitingForEnteringDecision. In the resolve() method for the transition delegate, use the isWaitingForExitingDecision to check if an exiting route actually needs an explicit decision on how to transition off the screen. If you try to make a decision for an existing route that isn’t waiting for a decision, Flutter throws an assertion error.

Context

When the navigator receives a new list of pages, it tries to update its current routes stack to match the list. However, it requires explicit decisions on how to transition the route on and off the screen. Previously, routes that were not in the new list required decisions on how to transition off the screen. However, we later found out this is not always true. If a route is popped, but is still waiting for the popping animation to finish, this route would sit in the navigator routes stack until the animation was done. If a page update occurred during this time, this route exits but doesn’t requires a decision on how to transition off the screen. Therefore, we added isWaitingForExitingDecision to cover that case.

The isEntering getter is also renamed to isWaitingForEnteringDecision to be more descriptive, and also to make the naming more consistent.

Migration guide

If you implement your own transition delegate, you need to check the exiting routes using the getter isWaitingForExitingDecision before you call markForPop, markForComplete, or markForRemove on them. You also need to rename all the references to isEntering to isWaitingForEnteringDecision.

Code before migration:

import 'package:flutter/widgets.dart';

class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
  @override
  Iterable<RouteTransitionRecord> resolve({
    List<RouteTransitionRecord> newPageRouteHistory,
    Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
    Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
  }) {
    final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];

    for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
      if (pageRoute.isEntering) {
        pageRoute.markForAdd();
      }
      results.add(pageRoute);

    }
    for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
      exitingPageRoute.markForRemove();
      final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
      if (pagelessRoutes != null) {
        for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
          pagelessRoute.markForRemove();
        }
      }
      results.add(exitingPageRoute);

    }
    return results;
  }
}

Code after migration:

import 'package:flutter/widgets.dart';

class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
  @override
  Iterable<RouteTransitionRecord> resolve({
    List<RouteTransitionRecord> newPageRouteHistory,
    Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
    Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
  }) {
    final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];

    for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
      // Renames isEntering to isWaitingForEnteringDecision.
      if (pageRoute.isWaitingForEnteringDecision) {
        pageRoute.markForAdd();
      }
      results.add(pageRoute);

    }
    for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
      // Checks the isWaitingForExitingDecision before calling the markFor methods.
      if (exitingPageRoute.isWaitingForExitingDecision) {
        exitingPageRoute.markForRemove();
        final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
        if (pagelessRoutes != null) {
          for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
            pagelessRoute.markForRemove();
          }
        }
      }
      results.add(exitingPageRoute);

    }
    return results;
  }
}

Timeline

Landed in version: 1.18.0
In stable release: not yet

References

API documentation:

Relevant issue:

Relevant PR:

  • PR 55998: Fixes the navigator pages update crash when there is still a route waiting