Skip to content
Boring Doc
BreakingHigh impactOfficial migration guidesince 1.17.0landed 1.16.3

Rebuild optimization for OverlayEntries and Routes

Framework · Official source

This optimization improves performance for route transitions, but it may uncover missing calls to `setState` in your app.

Original behavior

Prior to this change, an `OverlayEntry` would rebuild when a new opaque entry was added on top of it or removed above it. These rebuilds were unnecessary because they were not triggered by a change in state of the affected `OverlayEntry`. This breaking change optimized how we handle the addition and removal of `OverlayEntry`s, and removes unnecessary rebuilds to improve performance.

New behavior

In most cases, this change doesn't require any changes to your code. However, if your app was erroneously relying on the implicit rebuilds you may see issues, which can be resolved by wrapping any state change in a `setState` call.

Who is affected
Applications and packages relying on the previous behaviour or signature. Code that uses OverlayEntry, Navigator, Route and related APIs.
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+4−2
Before
1 class FooState extends State<Foo> {
2 String buttonLabel = 'Click Me';
3 @override
4 Widget build(BuildContext context) {
5 return ElevatedButton(
6 onPressed: () async {
7− // Illegal state modification that should be wrapped in setState.
8− buttonLabel = await Navigator.pushNamed(context, '/bar');
 
 
9 },
10 child: Text(buttonLabel),
11 );
12 }
13 }
After
1 class FooState extends State<Foo> {
2 String buttonLabel = 'Click Me';
3 @override
4 Widget build(BuildContext context) {
5 return ElevatedButton(
6 onPressed: () async {
7+ final newLabel = await Navigator.pushNamed(context, '/bar');
8+ setState(() {
9+ buttonLabel = newLabel;
10+ });
11 },
12 child: Text(buttonLabel),
13 );
14 }
15 }
Raw before / after
class FooState extends State<Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // Illegal state modification that should be wrapped in setState.
        buttonLabel = await Navigator.pushNamed(context, '/bar');
      },
      child: Text(buttonLabel),
    );
  }
}
class FooState extends State<Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        final newLabel = await Navigator.pushNamed(context, '/bar');
        setState(() {
          buttonLabel = newLabel;
        });
      },
      child: Text(buttonLabel),
    );
  }
}
Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)

If you're seeing issues after upgrading to a Flutter version that included this change, audit your code for missing calls to setState. In the example below, assigning the return value of Navigator.pushNamed to buttonLabel is implicitly modifying the state and it should be wrapped in an explicit setState call.

Code before migration:

class FooState extends State&#x3C;Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // Illegal state modification that should be wrapped in setState.
        buttonLabel = await Navigator.pushNamed(context, '/bar');
      },
      child: Text(buttonLabel),
    );
  }
}

Code after migration:

class FooState extends State&#x3C;Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        final newLabel = await Navigator.pushNamed(context, '/bar');
        setState(() {
          buttonLabel = newLabel;
        });
      },
      child: Text(buttonLabel),
    );
  }
}
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?