Unverified Commit 4fc78b93 authored by Gregory Conrad's avatar Gregory Conrad Committed by GitHub

Fix a memory leak in `AutomaticKeepAlive` (#124163)

Fix a memory leak in `AutomaticKeepAlive`
parent 509c2dde
...@@ -144,7 +144,8 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -144,7 +144,8 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
} }
VoidCallback _createCallback(Listenable handle) { VoidCallback _createCallback(Listenable handle) {
return () { late final VoidCallback callback;
return callback = () {
assert(() { assert(() {
if (!mounted) { if (!mounted) {
throw FlutterError( throw FlutterError(
...@@ -157,6 +158,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -157,6 +158,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
return true; return true;
}()); }());
_handles!.remove(handle); _handles!.remove(handle);
handle.removeListener(callback);
if (_handles!.isEmpty) { if (_handles!.isEmpty) {
if (SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) { if (SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) {
// Build/layout haven't started yet so let's just schedule this for // Build/layout haven't started yet so let's just schedule this for
......
...@@ -557,6 +557,26 @@ void main() { ...@@ -557,6 +557,26 @@ void main() {
expect(alternate.children.length, 1); expect(alternate.children.length, 1);
}); });
testWidgets('Keep alive Listenable has its listener removed once called', (WidgetTester tester) async {
final LeakCheckerHandle handle = LeakCheckerHandle();
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return const KeepAliveListenableLeakChecker(key: GlobalObjectKey<_KeepAliveListenableLeakCheckerState>(0));
},
),
));
final _KeepAliveListenableLeakCheckerState state = const GlobalObjectKey<_KeepAliveListenableLeakCheckerState>(0).currentState!;
expect(handle.hasListeners, false);
state.dispatch(handle);
expect(handle.hasListeners, true);
handle.notifyListeners();
expect(handle.hasListeners, false);
});
} }
class _AlwaysKeepAlive extends StatefulWidget { class _AlwaysKeepAlive extends StatefulWidget {
...@@ -633,3 +653,26 @@ class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with ...@@ -633,3 +653,26 @@ class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with
@override @override
void performLayout() { } void performLayout() { }
} }
class LeakCheckerHandle with ChangeNotifier {
@override
bool get hasListeners => super.hasListeners;
}
class KeepAliveListenableLeakChecker extends StatefulWidget {
const KeepAliveListenableLeakChecker({super.key});
@override
State<KeepAliveListenableLeakChecker> createState() => _KeepAliveListenableLeakCheckerState();
}
class _KeepAliveListenableLeakCheckerState extends State<KeepAliveListenableLeakChecker> {
void dispatch(Listenable handle) {
KeepAliveNotification(handle).dispatch(context);
}
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment