diff --git a/packages/flutter/lib/src/widgets/automatic_keep_alive.dart b/packages/flutter/lib/src/widgets/automatic_keep_alive.dart index 2ecbcf0c49..4b3b7cb6cc 100644 --- a/packages/flutter/lib/src/widgets/automatic_keep_alive.dart +++ b/packages/flutter/lib/src/widgets/automatic_keep_alive.dart @@ -144,7 +144,8 @@ class _AutomaticKeepAliveState extends State { } VoidCallback _createCallback(Listenable handle) { - return () { + late final VoidCallback callback; + return callback = () { assert(() { if (!mounted) { throw FlutterError( @@ -157,6 +158,7 @@ class _AutomaticKeepAliveState extends State { return true; }()); _handles!.remove(handle); + handle.removeListener(callback); if (_handles!.isEmpty) { if (SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) { // Build/layout haven't started yet so let's just schedule this for diff --git a/packages/flutter/test/widgets/automatic_keep_alive_test.dart b/packages/flutter/test/widgets/automatic_keep_alive_test.dart index 0c337a4c79..1daf6f0782 100644 --- a/packages/flutter/test/widgets/automatic_keep_alive_test.dart +++ b/packages/flutter/test/widgets/automatic_keep_alive_test.dart @@ -557,6 +557,26 @@ void main() { 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 { @@ -633,3 +653,26 @@ class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with @override void performLayout() { } } + +class LeakCheckerHandle with ChangeNotifier { + @override + bool get hasListeners => super.hasListeners; +} + +class KeepAliveListenableLeakChecker extends StatefulWidget { + const KeepAliveListenableLeakChecker({super.key}); + + @override + State createState() => _KeepAliveListenableLeakCheckerState(); +} + +class _KeepAliveListenableLeakCheckerState extends State { + void dispatch(Listenable handle) { + KeepAliveNotification(handle).dispatch(context); + } + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +}