@@ -1500,47 +1500,35 @@ class _OverlayPortalState extends State<OverlayPortal> {
15001500 // used as the slot of the overlay child widget.
15011501 //
15021502 // The developer must call `show` to reveal the overlay so we can get a unique
1503- // timestamp of the user interaction for sorting.
1503+ // timestamp of the user interaction for determining the z-index of the
1504+ // overlay child in the overlay.
15041505 //
15051506 // Avoid invalidating the cache if possible, since the framework uses `==` to
15061507 // compare slots, and _OverlayEntryLocation can't override that operator since
1507- // it's mutable.
1508+ // it's mutable. Changing slots can be relatively slow.
15081509 bool _childModelMayHaveChanged = true ;
15091510 _OverlayEntryLocation ? _locationCache;
1511+ static bool _isTheSameLocation (_OverlayEntryLocation locationCache, _RenderTheaterMarker marker) {
1512+ return locationCache._childModel == marker.overlayEntryWidgetState
1513+ && locationCache._theater == marker.theater;
1514+ }
1515+
15101516 _OverlayEntryLocation _getLocation (int zOrderIndex, bool targetRootOverlay) {
15111517 final _OverlayEntryLocation ? cachedLocation = _locationCache;
1512- if (cachedLocation != null && ! _childModelMayHaveChanged) {
1518+ late final _RenderTheaterMarker marker = _RenderTheaterMarker .of (context, targetRootOverlay: targetRootOverlay);
1519+ final bool isCacheValid = cachedLocation != null
1520+ && (! _childModelMayHaveChanged || _isTheSameLocation (cachedLocation, marker));
1521+ _childModelMayHaveChanged = false ;
1522+ if (isCacheValid) {
15131523 assert (cachedLocation._zOrderIndex == zOrderIndex);
1524+ assert (cachedLocation._debugIsLocationValid ());
15141525 return cachedLocation;
15151526 }
1516- _childModelMayHaveChanged = false ;
1517- final _RenderTheaterMarker ? marker = _RenderTheaterMarker .maybeOf (context, targetRootOverlay: targetRootOverlay);
1518- if (marker == null ) {
1519- throw FlutterError .fromParts (< DiagnosticsNode > [
1520- ErrorSummary ('No Overlay widget found.' ),
1521- ErrorDescription (
1522- '${widget .runtimeType } widgets require an Overlay widget ancestor.\n '
1523- 'An overlay lets widgets float on top of other widget children.' ,
1524- ),
1525- ErrorHint (
1526- 'To introduce an Overlay widget, you can either directly '
1527- 'include one, or use a widget that contains an Overlay itself, '
1528- 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.' ,
1529- ),
1530- ...context.describeMissingAncestor (expectedAncestorType: Overlay ),
1531- ]);
1532- }
1533- final _OverlayEntryLocation returnValue;
1534- if (cachedLocation == null ) {
1535- returnValue = _OverlayEntryLocation (zOrderIndex, marker.overlayEntryWidgetState, marker.theater);
1536- } else if (cachedLocation._childModel != marker.overlayEntryWidgetState || cachedLocation._theater != marker.theater) {
1537- cachedLocation._dispose ();
1538- returnValue = _OverlayEntryLocation (zOrderIndex, marker.overlayEntryWidgetState, marker.theater);
1539- } else {
1540- returnValue = cachedLocation;
1541- }
1542- assert (returnValue._zOrderIndex == zOrderIndex);
1543- return _locationCache = returnValue;
1527+ // Otherwise invalidate the cache and create a new location.
1528+ cachedLocation? ._debugMarkLocationInvalid ();
1529+ final _OverlayEntryLocation newLocation = _OverlayEntryLocation (zOrderIndex, marker.overlayEntryWidgetState, marker.theater);
1530+ assert (newLocation._zOrderIndex == zOrderIndex);
1531+ return _locationCache = newLocation;
15441532 }
15451533
15461534 @override
@@ -1582,7 +1570,7 @@ class _OverlayPortalState extends State<OverlayPortal> {
15821570 @override
15831571 void dispose () {
15841572 widget.controller._attachTarget = null ;
1585- _locationCache? ._dispose ();
1573+ _locationCache? ._debugMarkLocationInvalid ();
15861574 _locationCache = null ;
15871575 super .dispose ();
15881576 }
@@ -1593,14 +1581,14 @@ class _OverlayPortalState extends State<OverlayPortal> {
15931581 '${widget .controller .runtimeType }.show() should not be called during build.'
15941582 );
15951583 setState (() { _zOrderIndex = zOrderIndex; });
1596- _locationCache? ._dispose ();
1584+ _locationCache? ._debugMarkLocationInvalid ();
15971585 _locationCache = null ;
15981586 }
15991587
16001588 void hide () {
16011589 assert (SchedulerBinding .instance.schedulerPhase != SchedulerPhase .persistentCallbacks);
16021590 setState (() { _zOrderIndex = null ; });
1603- _locationCache? ._dispose ();
1591+ _locationCache? ._debugMarkLocationInvalid ();
16041592 _locationCache = null ;
16051593 }
16061594
@@ -1681,7 +1669,7 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation>
16811669 }
16821670
16831671 void _addChild (_RenderDeferredLayoutBox child) {
1684- assert (_debugNotDisposed ());
1672+ assert (_debugIsLocationValid ());
16851673 _addToChildModel (child);
16861674 _theater._addDeferredChild (child);
16871675 assert (child.parent == _theater);
@@ -1696,7 +1684,7 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation>
16961684
16971685 void _moveChild (_RenderDeferredLayoutBox child, _OverlayEntryLocation fromLocation) {
16981686 assert (fromLocation != this );
1699- assert (_debugNotDisposed ());
1687+ assert (_debugIsLocationValid ());
17001688 final _RenderTheater fromTheater = fromLocation._theater;
17011689 final _OverlayEntryWidgetState fromModel = fromLocation._childModel;
17021690
@@ -1712,34 +1700,54 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation>
17121700 }
17131701
17141702 void _activate (_RenderDeferredLayoutBox child) {
1715- assert (_debugNotDisposed ());
1703+ // This call is allowed even when this location is invalidated.
1704+ // See _OverlayPortalElement.activate.
17161705 assert (_overlayChildRenderBox == null , '$_overlayChildRenderBox ' );
17171706 _theater._adoptDeferredLayoutBoxChild (child);
17181707 _overlayChildRenderBox = child;
17191708 }
17201709
17211710 void _deactivate (_RenderDeferredLayoutBox child) {
1722- assert ( _debugNotDisposed ());
1711+ // This call is allowed even when this location is invalidated.
17231712 _theater._dropDeferredLayoutBoxChild (child);
17241713 _overlayChildRenderBox = null ;
17251714 }
17261715
1727- bool _debugNotDisposed () {
1728- if (_debugDisposedStackTrace == null ) {
1716+ // Throws a StateError if this location is already invalidated and shouldn't
1717+ // be used as an OverlayPortal slot. Must be used in asserts.
1718+ //
1719+ // Generally, `assert(_debugIsLocationValid())` should be used to prevent
1720+ // invalid accesses to an invalid `_OverlayEntryLocation` object. Exceptions
1721+ // to this rule are _removeChild, _deactive, which will be called when the
1722+ // OverlayPortal is being removed from the widget tree and may use the
1723+ // location information to perform cleanup tasks.
1724+ //
1725+ // Another exception is the _activate method which is called by
1726+ // _OverlayPortalElement.activate. See the comment in _OverlayPortalElement.activate.
1727+ bool _debugIsLocationValid () {
1728+ if (_debugMarkLocationInvalidStackTrace == null ) {
17291729 return true ;
17301730 }
1731- throw StateError ('$this is already disposed. Stack trace: $_debugDisposedStackTrace ' );
1731+ throw StateError ('$this is already disposed. Stack trace: $_debugMarkLocationInvalidStackTrace ' );
17321732 }
17331733
1734- StackTrace ? _debugDisposedStackTrace;
1734+ // The StackTrace of the first _debugMarkLocationInvalid call. It's only for
1735+ // debugging purposes and the StackTrace will only be captured in debug builds.
1736+ //
1737+ // The effect of this method is not reversible. Once marked invalid, this
1738+ // object can't be marked as valid again.
1739+ StackTrace ? _debugMarkLocationInvalidStackTrace;
17351740 @mustCallSuper
1736- void _dispose () {
1737- assert (_debugNotDisposed ());
1741+ void _debugMarkLocationInvalid () {
1742+ assert (_debugIsLocationValid ());
17381743 assert (() {
1739- _debugDisposedStackTrace = StackTrace .current;
1744+ _debugMarkLocationInvalidStackTrace = StackTrace .current;
17401745 return true ;
17411746 }());
17421747 }
1748+
1749+ @override
1750+ String toString () => '${objectRuntimeType (this , '_OverlayEntryLocation' )}[${shortHash (this )}] ${_debugMarkLocationInvalidStackTrace != null ? "(INVALID)" :"" }' ;
17431751}
17441752
17451753class _RenderTheaterMarker extends InheritedWidget {
@@ -1758,13 +1766,31 @@ class _RenderTheaterMarker extends InheritedWidget {
17581766 || oldWidget.overlayEntryWidgetState != overlayEntryWidgetState;
17591767 }
17601768
1761- static _RenderTheaterMarker ? maybeOf (BuildContext context, { bool targetRootOverlay = false }) {
1769+ static _RenderTheaterMarker of (BuildContext context, { bool targetRootOverlay = false }) {
1770+ final _RenderTheaterMarker ? marker;
17621771 if (targetRootOverlay) {
17631772 final InheritedElement ? ancestor = _rootRenderTheaterMarkerOf (context.getElementForInheritedWidgetOfExactType <_RenderTheaterMarker >());
17641773 assert (ancestor == null || ancestor.widget is _RenderTheaterMarker );
1765- return ancestor != null ? context.dependOnInheritedElement (ancestor) as _RenderTheaterMarker ? : null ;
1774+ marker = ancestor != null ? context.dependOnInheritedElement (ancestor) as _RenderTheaterMarker ? : null ;
1775+ } else {
1776+ marker = context.dependOnInheritedWidgetOfExactType <_RenderTheaterMarker >();
1777+ }
1778+ if (marker != null ) {
1779+ return marker;
17661780 }
1767- return context.dependOnInheritedWidgetOfExactType <_RenderTheaterMarker >();
1781+ throw FlutterError .fromParts (< DiagnosticsNode > [
1782+ ErrorSummary ('No Overlay widget found.' ),
1783+ ErrorDescription (
1784+ '${context .widget .runtimeType } widgets require an Overlay widget ancestor.\n '
1785+ 'An overlay lets widgets float on top of other widget children.' ,
1786+ ),
1787+ ErrorHint (
1788+ 'To introduce an Overlay widget, you can either directly '
1789+ 'include one, or use a widget that contains an Overlay itself, '
1790+ 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.' ,
1791+ ),
1792+ ...context.describeMissingAncestor (expectedAncestorType: Overlay ),
1793+ ]);
17681794 }
17691795
17701796 static InheritedElement ? _rootRenderTheaterMarkerOf (InheritedElement ? theaterMarkerElement) {
@@ -1792,7 +1818,7 @@ class _OverlayPortal extends RenderObjectWidget {
17921818 required this .overlayChild,
17931819 required this .child,
17941820 }) : assert (overlayChild == null || overlayLocation != null ),
1795- assert (overlayLocation == null || overlayLocation._debugNotDisposed ());
1821+ assert (overlayLocation == null || overlayLocation._debugIsLocationValid ());
17961822
17971823 final Widget ? overlayChild;
17981824
@@ -1863,6 +1889,9 @@ class _OverlayPortalElement extends RenderObjectElement {
18631889 if (box != null ) {
18641890 assert (! box.attached);
18651891 assert (renderObject._deferredLayoutChild == box);
1892+ // updateChild has not been called at this point so the RenderTheater in
1893+ // the overlay location could be detached. Adding children to a detached
1894+ // RenderObject is still allowed however this isn't the most efficient.
18661895 (overlayChild.slot! as _OverlayEntryLocation )._activate (box);
18671896 }
18681897 }
@@ -1872,12 +1901,8 @@ class _OverlayPortalElement extends RenderObjectElement {
18721901 void deactivate () {
18731902 final Element ? overlayChild = _overlayChild;
18741903 // Instead of just detaching the render objects, removing them from the
1875- // render subtree entirely such that if the widget gets reparented to a
1876- // different overlay entry, the overlay child is inserted in the right
1877- // position in the overlay's child list.
1878- //
1879- // This is also a workaround for the !renderObject.attached assert in the
1880- // `RenderObjectElement.deactive()` method.
1904+ // render subtree entirely. This is a workaround for the
1905+ // !renderObject.attached assert in the `super.deactive()` method.
18811906 if (overlayChild != null ) {
18821907 final _RenderDeferredLayoutBox ? box = overlayChild.renderObject as _RenderDeferredLayoutBox ? ;
18831908 if (box != null ) {
@@ -1902,7 +1927,7 @@ class _OverlayPortalElement extends RenderObjectElement {
19021927 // reparenting between _overlayChild and _child, thus the non-null-typed slots.
19031928 @override
19041929 void moveRenderObjectChild (_RenderDeferredLayoutBox child, _OverlayEntryLocation oldSlot, _OverlayEntryLocation newSlot) {
1905- assert (newSlot._debugNotDisposed ());
1930+ assert (newSlot._debugIsLocationValid ());
19061931 newSlot._moveChild (child, oldSlot);
19071932 }
19081933
0 commit comments