Rebuild optimization for OverlayEntries and Routes
Summary
#
                  This optimization improves performance for route transitions,
                  but it may uncover missing calls to setState in your app.
                
Context
#
                  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
                  OverlayEntrys, and removes unnecessary rebuilds
                  to improve performance.
                
                  Since the Navigator internally puts each Route into an
                  OverlayEntry this change also applies to Route transitions:
                  If an opaque Route is pushed on top or removed from above another
                  Route, the Routes below the opaque Route
                  no longer rebuilds unnecessarily.
                
Description of change
#
                  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.
                
                  Furthermore, this change slightly modified the shape of the
                  widget tree: Prior to this change,
                  the OverlayEntrys were wrapped in a Stack widget.
                  The explicit Stack widget was removed from the widget hierarchy.
                
Migration guide
#
                  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<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<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),
    );
  }
}
                    
                    
                    
                  Timeline
#
                  Landed in version: 1.16.3
                  In stable release: 1.17
                
References
#API documentation:
Relevant issues:
Relevant PRs:
Unless stated otherwise, the documentation on this site reflects Flutter 3.35.5. Page last updated on 2025-10-28. View source or report an issue.