Unverified Commit 994a19ec authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Clear listeners when AnimationController is disposed (#79998)

parent 97abe441
......@@ -801,6 +801,8 @@ class AnimationController extends Animation<double>
}());
_ticker!.dispose();
_ticker = null;
clearStatusListeners();
clearListeners();
super.dispose();
}
......
......@@ -589,6 +589,8 @@ class TrainHoppingAnimation extends Animation<double>
_currentTrain = null;
_nextTrain?.removeListener(_valueChangeHandler);
_nextTrain = null;
clearListeners();
clearStatusListeners();
super.dispose();
}
......
......@@ -23,6 +23,7 @@ mixin AnimationLazyListenerMixin {
/// * [didUnregisterListener], which may cause the listener list to
/// become empty again, and in turn cause this method to call
/// [didStartListening] again.
@protected
void didRegisterListener() {
assert(_listenerCounter >= 0);
if (_listenerCounter == 0)
......@@ -36,6 +37,7 @@ mixin AnimationLazyListenerMixin {
/// See also:
///
/// * [didRegisterListener], which causes the listener list to become non-empty.
@protected
void didUnregisterListener() {
assert(_listenerCounter >= 1);
_listenerCounter -= 1;
......@@ -63,9 +65,11 @@ mixin AnimationLazyListenerMixin {
/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
mixin AnimationEagerListenerMixin {
/// This implementation ignores listener registrations.
@protected
void didRegisterListener() { }
/// This implementation ignores listener registrations.
@protected
void didUnregisterListener() { }
/// Release the resources used by this object. The object is no longer usable
......@@ -87,12 +91,14 @@ mixin AnimationLocalListenersMixin {
///
/// At the time this method is called the registered listener is not yet
/// notified by [notifyListeners].
@protected
void didRegisterListener();
/// Called immediately after a listener is removed via [removeListener].
///
/// At the time this method is called the removed listener is no longer
/// notified by [notifyListeners].
@protected
void didUnregisterListener();
/// Calls the listener every time the value of the animation changes.
......@@ -113,10 +119,22 @@ mixin AnimationLocalListenersMixin {
}
}
/// Removes all listeners added with [addListener].
///
/// This method is typically called from the `dispose` method of the class
/// using this mixin if the class also uses the [AnimationEagerListenerMixin].
///
/// Calling this method will not trigger [didUnregisterListener].
@protected
void clearListeners() {
_listeners.clear();
}
/// Calls all the listeners.
///
/// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration.
@protected
void notifyListeners() {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) {
......@@ -161,12 +179,14 @@ mixin AnimationLocalStatusListenersMixin {
///
/// At the time this method is called the registered listener is not yet
/// notified by [notifyStatusListeners].
@protected
void didRegisterListener();
/// Called immediately after a status listener is removed via [removeStatusListener].
///
/// At the time this method is called the removed listener is no longer
/// notified by [notifyStatusListeners].
@protected
void didUnregisterListener();
/// Calls listener every time the status of the animation changes.
......@@ -187,10 +207,22 @@ mixin AnimationLocalStatusListenersMixin {
}
}
/// Removes all listeners added with [addStatusListener].
///
/// This method is typically called from the `dispose` method of the class
/// using this mixin if the class also uses the [AnimationEagerListenerMixin].
///
/// Calling this method will not trigger [didUnregisterListener].
@protected
void clearStatusListeners() {
_statusListeners.clear();
}
/// Calls all the status listeners.
///
/// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration.
@protected
void notifyStatusListeners(AnimationStatus status) {
final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
for (final AnimationStatusListener listener in localListeners) {
......
......@@ -48,6 +48,13 @@ class ObserverList<T> extends Iterable<T> {
return _list.remove(item);
}
/// Removes all items from the list.
void clear() {
_isDirty = false;
_list.clear();
_set.clear();
}
@override
bool contains(Object? element) {
if (_list.length < 3)
......
......@@ -416,6 +416,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
@override
void dispose() {
assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
_animation?.removeStatusListener(_handleStatusChanged);
_controller?.dispose();
_transitionCompleter.complete(_result);
super.dispose();
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/animation.dart';
void main() {
test('Disposing controller removes listeners to avoid memory leaks', () {
final _TestAnimationController controller = _TestAnimationController(
duration: const Duration(milliseconds: 100),
vsync: const TestVSync(),
);
int statusListener = 0;
int listener = 0;
controller.addListener(() {
listener++;
});
controller.addStatusListener((AnimationStatus _) {
statusListener++;
});
expect(statusListener, 0);
expect(listener, 0);
controller.publicNotifyListeners();
controller.publicNotifyStatusListeners(AnimationStatus.completed);
expect(statusListener, 1);
expect(listener, 1);
controller.dispose();
controller.publicNotifyListeners();
controller.publicNotifyStatusListeners(AnimationStatus.completed);
expect(statusListener, 1);
expect(listener, 1);
});
}
class _TestAnimationController extends AnimationController {
_TestAnimationController({
double? value,
Duration? duration,
Duration? reverseDuration,
String? debugLabel,
double lowerBound = 0.0,
double upperBound = 1.0,
AnimationBehavior animationBehavior = AnimationBehavior.normal,
required TickerProvider vsync,
}) : super(
value: value,
duration: duration,
reverseDuration: reverseDuration,
debugLabel: debugLabel,
lowerBound: lowerBound,
upperBound: upperBound,
animationBehavior: animationBehavior,
vsync: vsync,
);
void publicNotifyListeners() {
super.notifyListeners();
}
void publicNotifyStatusListeners(AnimationStatus status) {
super.notifyStatusListeners(status);
}
}
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