diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index bc3d294156..c2c160dbd2 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -2056,7 +2056,8 @@ abstract class BuildContext { /// This method will only return a valid result after the build phase is /// complete. It is therefore not valid to call this from a build method. /// It should only be called from interaction event handlers (e.g. - /// gesture callbacks) or layout or paint callbacks. + /// gesture callbacks) or layout or paint callbacks. It is also not valid to + /// call if [State.mounted] returns false. /// /// If the render object is a [RenderBox], which is the common case, then the /// size of the render object can be obtained from the [size] getter. This is @@ -3864,7 +3865,25 @@ abstract class Element extends DiagnosticableTree implements BuildContext { } @override - RenderObject? findRenderObject() => renderObject; + RenderObject? findRenderObject() { + assert(() { + if (_lifecycleState != _ElementLifecycle.active) { + throw FlutterError.fromParts([ + ErrorSummary('Cannot get renderObject of inactive element.'), + ErrorDescription( + 'In order for an element to have a valid renderObject, it must be ' + 'active, which means it is part of the tree.\n' + 'Instead, this element is in the $_lifecycleState state.\n' + 'If you called this method from a State object, consider guarding ' + 'it with State.mounted.', + ), + describeElement('The findRenderObject() method was called for the following element'), + ]); + } + return true; + }()); + return renderObject; + } @override Size? get size { diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index ff684a46fe..6ee4a60b3f 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -2455,6 +2455,11 @@ class InspectorSelection { Element? _currentElement; set currentElement(Element? element) { + if (element?.debugIsDefunct == true) { + _currentElement = null; + _current = null; + return; + } if (currentElement != element) { _currentElement = element; _current = element!.findRenderObject(); diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 9a58ce23b7..86067ff4fa 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1619,6 +1619,30 @@ void main() { await pumpWidget(Container()); expect(states, ['deactivate', 'dispose']); }); + + testWidgets('Getting the render object of an unmounted element throws', (WidgetTester tester) async { + await tester.pumpWidget(const _StatefulLeaf()); + final StatefulElement element = tester.element(find.byType(_StatefulLeaf)); + expect(element.state, isA>()); + expect(element.widget, isA<_StatefulLeaf>()); + // Replace the widget tree to unmount the element. + await tester.pumpWidget(Container()); + + expect( + () => element.findRenderObject(), + throwsA(isA().having( + (FlutterError error) => error.message, + 'message', + equalsIgnoringHashCodes(''' +Cannot get renderObject of inactive element. +In order for an element to have a valid renderObject, it must be active, which means it is part of the tree. +Instead, this element is in the _ElementLifecycle.defunct state. +If you called this method from a State object, consider guarding it with State.mounted. +The findRenderObject() method was called for the following element: + StatefulElement#00000(DEFUNCT)'''), + )), + ); + }); } class _WidgetWithNoVisitChildren extends StatelessWidget {