Skip to content

Commit b5fb665

Browse files
authored
InheritedElement.removeDependent() (#129210)
Call the `dependency.removeDependent(this)` instead of `dependency._dependents.remove(this)` inside the `Element.deactivate()`. This allows `InheritedElements` to know when they can release resources associated with a given dependent `Element`. Fixes #129207
1 parent 8a0f911 commit b5fb665

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

packages/flutter/lib/src/widgets/framework.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4488,7 +4488,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
44884488
assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
44894489
if (_dependencies != null && _dependencies!.isNotEmpty) {
44904490
for (final InheritedElement dependency in _dependencies!) {
4491-
dependency._dependents.remove(this);
4491+
dependency.removeDependent(this);
44924492
}
44934493
// For expediency, we don't actually clear the list here, even though it's
44944494
// no longer representative of what we are registered with. If we never
@@ -6024,6 +6024,19 @@ class InheritedElement extends ProxyElement {
60246024
dependent.didChangeDependencies();
60256025
}
60266026

6027+
/// Called by [Element.deactivate] to remove the provided `dependent` [Element] from this [InheritedElement].
6028+
///
6029+
/// After the dependent is removed, [Element.didChangeDependencies] will no
6030+
/// longer be called on it when this [InheritedElement] notifies its dependents.
6031+
///
6032+
/// Subclasses can override this method to release any resources retained for
6033+
/// a given [dependent].
6034+
@protected
6035+
@mustCallSuper
6036+
void removeDependent(Element dependent) {
6037+
_dependents.remove(dependent);
6038+
}
6039+
60276040
/// Calls [Element.didChangeDependencies] of all dependent elements, if
60286041
/// [InheritedWidget.updateShouldNotify] returns true.
60296042
///

packages/flutter/test/widgets/framework_test.dart

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,56 @@ void main() {
16961696
expect(states, <String>['deactivate', 'dispose']);
16971697
});
16981698

1699+
testWidgetsWithLeakTracking('Element.deactivate reports its deactivation to the InheritedElement it depends on', (WidgetTester tester) async {
1700+
final List<Key> removedDependentWidgetKeys = <Key>[];
1701+
1702+
InheritedElement elementCreator(InheritedWidget widget) {
1703+
return _InheritedElementSpy(
1704+
widget,
1705+
onRemoveDependent: (Element dependent) {
1706+
removedDependentWidgetKeys.add(dependent.widget.key!);
1707+
},
1708+
);
1709+
}
1710+
1711+
Widget builder(BuildContext context) {
1712+
context.dependOnInheritedWidgetOfExactType<Inherited>();
1713+
return Container();
1714+
}
1715+
1716+
await tester.pumpWidget(
1717+
Inherited(
1718+
0,
1719+
elementCreator: elementCreator,
1720+
child: Column(
1721+
children: <Widget>[
1722+
Builder(
1723+
key: const Key('dependent'),
1724+
builder: builder,
1725+
),
1726+
],
1727+
),
1728+
),
1729+
);
1730+
1731+
expect(removedDependentWidgetKeys, isEmpty);
1732+
1733+
await tester.pumpWidget(
1734+
Inherited(
1735+
0,
1736+
elementCreator: elementCreator,
1737+
child: Column(
1738+
children: <Widget>[
1739+
Container(),
1740+
],
1741+
),
1742+
),
1743+
);
1744+
1745+
expect(removedDependentWidgetKeys, hasLength(1));
1746+
expect(removedDependentWidgetKeys.first, const Key('dependent'));
1747+
});
1748+
16991749
testWidgetsWithLeakTracking('RenderObjectElement.unmount disposes of its renderObject', (WidgetTester tester) async {
17001750
await tester.pumpWidget(const Placeholder());
17011751
final RenderObjectElement element = tester.allElements.whereType<RenderObjectElement>().last;
@@ -1902,12 +1952,33 @@ class DirtyElementWithCustomBuildOwner extends Element {
19021952
}
19031953

19041954
class Inherited extends InheritedWidget {
1905-
const Inherited(this.value, {super.key, required super.child});
1955+
const Inherited(this.value, {super.key, required super.child, this.elementCreator});
19061956

19071957
final int? value;
1958+
final InheritedElement Function(Inherited widget)? elementCreator;
19081959

19091960
@override
19101961
bool updateShouldNotify(Inherited oldWidget) => oldWidget.value != value;
1962+
1963+
@override
1964+
InheritedElement createElement() {
1965+
if (elementCreator != null) {
1966+
return elementCreator!(this);
1967+
}
1968+
return super.createElement();
1969+
}
1970+
}
1971+
1972+
class _InheritedElementSpy extends InheritedElement {
1973+
_InheritedElementSpy(super.widget, {this.onRemoveDependent});
1974+
1975+
final void Function(Element element)? onRemoveDependent;
1976+
1977+
@override
1978+
void removeDependent(Element dependent) {
1979+
super.removeDependent(dependent);
1980+
onRemoveDependent?.call(dependent);
1981+
}
19111982
}
19121983

19131984
class DependentStatefulWidget extends StatefulWidget {

0 commit comments

Comments
 (0)