title: Rebuild optimization for OverlayEntries and Routes title: OverlayEntries 和 Routes 进行了重建优化 description: OverlayEntries only rebuild on explicit state changes. description: OverlayEntries 仅在显式状态更改时重建。 =======

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 change optimized how we handle the addition and removal of OverlayEntrys and removes the 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 rebuild 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 RaisedButton(
      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 RaisedButton(
      onPressed: () async {
        final newLabel = await Navigator.pushNamed(context, '/bar');
        setState(() {
          buttonLabel = newLabel;
        });
      },
      child: Text(buttonLabel),
    );
  }
}

Timeline

This change was made in January of 2020 after the v1.14.3 release.

References

API documentation:

Relevant issues:

Relevant PRs: