diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 0480896bb0..357660668c 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4488,7 +4488,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { assert(_widget != null); // Use the private property to avoid a CastError during hot reload. if (_dependencies != null && _dependencies!.isNotEmpty) { for (final InheritedElement dependency in _dependencies!) { - dependency._dependents.remove(this); + dependency.removeDependent(this); } // For expediency, we don't actually clear the list here, even though it's // no longer representative of what we are registered with. If we never @@ -6024,6 +6024,19 @@ class InheritedElement extends ProxyElement { dependent.didChangeDependencies(); } + /// Called by [Element.deactivate] to remove the provided `dependent` [Element] from this [InheritedElement]. + /// + /// After the dependent is removed, [Element.didChangeDependencies] will no + /// longer be called on it when this [InheritedElement] notifies its dependents. + /// + /// Subclasses can override this method to release any resources retained for + /// a given [dependent]. + @protected + @mustCallSuper + void removeDependent(Element dependent) { + _dependents.remove(dependent); + } + /// Calls [Element.didChangeDependencies] of all dependent elements, if /// [InheritedWidget.updateShouldNotify] returns true. /// diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index d80e1e5667..b4227c5c60 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1696,6 +1696,56 @@ void main() { expect(states, ['deactivate', 'dispose']); }); + testWidgetsWithLeakTracking('Element.deactivate reports its deactivation to the InheritedElement it depends on', (WidgetTester tester) async { + final List removedDependentWidgetKeys = []; + + InheritedElement elementCreator(InheritedWidget widget) { + return _InheritedElementSpy( + widget, + onRemoveDependent: (Element dependent) { + removedDependentWidgetKeys.add(dependent.widget.key!); + }, + ); + } + + Widget builder(BuildContext context) { + context.dependOnInheritedWidgetOfExactType(); + return Container(); + } + + await tester.pumpWidget( + Inherited( + 0, + elementCreator: elementCreator, + child: Column( + children: [ + Builder( + key: const Key('dependent'), + builder: builder, + ), + ], + ), + ), + ); + + expect(removedDependentWidgetKeys, isEmpty); + + await tester.pumpWidget( + Inherited( + 0, + elementCreator: elementCreator, + child: Column( + children: [ + Container(), + ], + ), + ), + ); + + expect(removedDependentWidgetKeys, hasLength(1)); + expect(removedDependentWidgetKeys.first, const Key('dependent')); + }); + testWidgetsWithLeakTracking('RenderObjectElement.unmount disposes of its renderObject', (WidgetTester tester) async { await tester.pumpWidget(const Placeholder()); final RenderObjectElement element = tester.allElements.whereType().last; @@ -1902,12 +1952,33 @@ class DirtyElementWithCustomBuildOwner extends Element { } class Inherited extends InheritedWidget { - const Inherited(this.value, {super.key, required super.child}); + const Inherited(this.value, {super.key, required super.child, this.elementCreator}); final int? value; + final InheritedElement Function(Inherited widget)? elementCreator; @override bool updateShouldNotify(Inherited oldWidget) => oldWidget.value != value; + + @override + InheritedElement createElement() { + if (elementCreator != null) { + return elementCreator!(this); + } + return super.createElement(); + } +} + +class _InheritedElementSpy extends InheritedElement { + _InheritedElementSpy(super.widget, {this.onRemoveDependent}); + + final void Function(Element element)? onRemoveDependent; + + @override + void removeDependent(Element dependent) { + super.removeDependent(dependent); + onRemoveDependent?.call(dependent); + } } class DependentStatefulWidget extends StatefulWidget {