Skip to content
Boring Doc
BreakingHigh impactOfficial migration guidesince 1.20.0landed 1.18.0

The Route Transition record and Transition delegate updates

Framework · Official source

A new boolean getter `isWaitingForExitingDecision` was added to the route transition record and the `isEntering` getter was renamed 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.

Original behavior

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 re

New behavior

Not stated in the source.

Who is affected
Applications and packages relying on the previous behaviour or signature. Code that uses Navigator, TransitionDelegate, RouteTransitionRecord.
Practical impact
Behaviour or API differences that can break builds or change runtime behaviour.
What action should I take?
Follow the official migration guide below, update affected call sites, run `dart fix --apply` where a fix is available, then run your tests.

Before and after (from the official guide)

dart+10−6
Before
1 import 'package:flutter/widgets.dart';
2  
3 class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
4 @override
5 Iterable<RouteTransitionRecord> resolve({
6 List<RouteTransitionRecord> newPageRouteHistory,
7 Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
8 Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
9 }) {
10 final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
11  
12 for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
13− if (pageRoute.isEntering) {
 
14 pageRoute.markForAdd();
15 }
16 results.add(pageRoute);
17  
18 }
19 for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
20− exitingPageRoute.markForRemove();
21− final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
22− if (pagelessRoutes != null) {
23− for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
24− pagelessRoute.markForRemove();
 
 
 
25 }
26 }
27 results.add(exitingPageRoute);
28  
29 }
30 return results;
31 }
32 }
After
1 import 'package:flutter/widgets.dart';
2  
3 class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
4 @override
5 Iterable<RouteTransitionRecord> resolve({
6 List<RouteTransitionRecord> newPageRouteHistory,
7 Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
8 Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
9 }) {
10 final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
11  
12 for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
13+ // Renames isEntering to isWaitingForEnteringDecision.
14+ if (pageRoute.isWaitingForEnteringDecision) {
15 pageRoute.markForAdd();
16 }
17 results.add(pageRoute);
18  
19 }
20 for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
21+ // Checks the isWaitingForExitingDecision before calling the markFor methods.
22+ if (exitingPageRoute.isWaitingForExitingDecision) {
23+ exitingPageRoute.markForRemove();
24+ final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
25+ if (pagelessRoutes != null) {
26+ for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
27+ pagelessRoute.markForRemove();
28+ }
29 }
30 }
31 results.add(exitingPageRoute);
32  
33 }
34 return results;
35 }
36 }
Raw before / after
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;
  }
}
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;
  }
}
Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)

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 from isEntering to isWaitingForEnteringDecision.

Code before migration:

import 'package:flutter/widgets.dart';

class NoAnimationTransitionDelegate extends TransitionDelegate&#x3C;void> {
  @override
  Iterable&#x3C;RouteTransitionRecord> resolve({
    List&#x3C;RouteTransitionRecord> newPageRouteHistory,
    Map&#x3C;RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
    Map&#x3C;RouteTransitionRecord, List&#x3C;RouteTransitionRecord>> pageRouteToPagelessRoutes,
  }) {
    final List&#x3C;RouteTransitionRecord> results = &#x3C;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&#x3C;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&#x3C;void> {
  @override
  Iterable&#x3C;RouteTransitionRecord> resolve({
    List&#x3C;RouteTransitionRecord> newPageRouteHistory,
    Map&#x3C;RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
    Map&#x3C;RouteTransitionRecord, List&#x3C;RouteTransitionRecord>> pageRouteToPagelessRoutes,
  }) {
    final List&#x3C;RouteTransitionRecord> results = &#x3C;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&#x3C;RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
        if (pagelessRoutes != null) {
          for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
            pagelessRoute.markForRemove();
          }
        }
      }
      results.add(exitingPageRoute);

    }
    return results;
  }
}
Migration assistantAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.
Official migration guide·Verified classification (95%)

Listed in the official breaking-changes index (docs.flutter.dev/release/breaking-changes).

An official announcement or migration guide documents an intended behaviour or API change. A verified source change (git diff) shows code changed but does not by itself prove a public API is breaking. AI interpretations are labelled and never used to classify a change.

Source commit
b3045439b7c0
Source last updated
Oct 21, 2026
Last synchronized
Oct 21, 2026
Publication status
published
Publication history
  • Oct 21, 2026, 8:31 PM: new → published (ingestion) — Backed by an official breaking-change/migration guide.
AI explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.
Was this useful?