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> ...@@ -801,6 +801,8 @@ class AnimationController extends Animation<double>
}()); }());
_ticker!.dispose(); _ticker!.dispose();
_ticker = null; _ticker = null;
clearStatusListeners();
clearListeners();
super.dispose(); super.dispose();
} }
......
...@@ -589,6 +589,8 @@ class TrainHoppingAnimation extends Animation<double> ...@@ -589,6 +589,8 @@ class TrainHoppingAnimation extends Animation<double>
_currentTrain = null; _currentTrain = null;
_nextTrain?.removeListener(_valueChangeHandler); _nextTrain?.removeListener(_valueChangeHandler);
_nextTrain = null; _nextTrain = null;
clearListeners();
clearStatusListeners();
super.dispose(); super.dispose();
} }
......
...@@ -23,6 +23,7 @@ mixin AnimationLazyListenerMixin { ...@@ -23,6 +23,7 @@ mixin AnimationLazyListenerMixin {
/// * [didUnregisterListener], which may cause the listener list to /// * [didUnregisterListener], which may cause the listener list to
/// become empty again, and in turn cause this method to call /// become empty again, and in turn cause this method to call
/// [didStartListening] again. /// [didStartListening] again.
@protected
void didRegisterListener() { void didRegisterListener() {
assert(_listenerCounter >= 0); assert(_listenerCounter >= 0);
if (_listenerCounter == 0) if (_listenerCounter == 0)
...@@ -36,6 +37,7 @@ mixin AnimationLazyListenerMixin { ...@@ -36,6 +37,7 @@ mixin AnimationLazyListenerMixin {
/// See also: /// See also:
/// ///
/// * [didRegisterListener], which causes the listener list to become non-empty. /// * [didRegisterListener], which causes the listener list to become non-empty.
@protected
void didUnregisterListener() { void didUnregisterListener() {
assert(_listenerCounter >= 1); assert(_listenerCounter >= 1);
_listenerCounter -= 1; _listenerCounter -= 1;
...@@ -63,9 +65,11 @@ mixin AnimationLazyListenerMixin { ...@@ -63,9 +65,11 @@ mixin AnimationLazyListenerMixin {
/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin]. /// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
mixin AnimationEagerListenerMixin { mixin AnimationEagerListenerMixin {
/// This implementation ignores listener registrations. /// This implementation ignores listener registrations.
@protected
void didRegisterListener() { } void didRegisterListener() { }
/// This implementation ignores listener registrations. /// This implementation ignores listener registrations.
@protected
void didUnregisterListener() { } void didUnregisterListener() { }
/// Release the resources used by this object. The object is no longer usable /// Release the resources used by this object. The object is no longer usable
...@@ -87,12 +91,14 @@ mixin AnimationLocalListenersMixin { ...@@ -87,12 +91,14 @@ mixin AnimationLocalListenersMixin {
/// ///
/// At the time this method is called the registered listener is not yet /// At the time this method is called the registered listener is not yet
/// notified by [notifyListeners]. /// notified by [notifyListeners].
@protected
void didRegisterListener(); void didRegisterListener();
/// Called immediately after a listener is removed via [removeListener]. /// Called immediately after a listener is removed via [removeListener].
/// ///
/// At the time this method is called the removed listener is no longer /// At the time this method is called the removed listener is no longer
/// notified by [notifyListeners]. /// notified by [notifyListeners].
@protected
void didUnregisterListener(); void didUnregisterListener();
/// Calls the listener every time the value of the animation changes. /// Calls the listener every time the value of the animation changes.
...@@ -113,10 +119,22 @@ mixin AnimationLocalListenersMixin { ...@@ -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. /// Calls all the listeners.
/// ///
/// If listeners are added or removed during this function, the modifications /// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration. /// will not change which listeners are called during this iteration.
@protected
void notifyListeners() { void notifyListeners() {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners); final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) { for (final VoidCallback listener in localListeners) {
...@@ -161,12 +179,14 @@ mixin AnimationLocalStatusListenersMixin { ...@@ -161,12 +179,14 @@ mixin AnimationLocalStatusListenersMixin {
/// ///
/// At the time this method is called the registered listener is not yet /// At the time this method is called the registered listener is not yet
/// notified by [notifyStatusListeners]. /// notified by [notifyStatusListeners].
@protected
void didRegisterListener(); void didRegisterListener();
/// Called immediately after a status listener is removed via [removeStatusListener]. /// Called immediately after a status listener is removed via [removeStatusListener].
/// ///
/// At the time this method is called the removed listener is no longer /// At the time this method is called the removed listener is no longer
/// notified by [notifyStatusListeners]. /// notified by [notifyStatusListeners].
@protected
void didUnregisterListener(); void didUnregisterListener();
/// Calls listener every time the status of the animation changes. /// Calls listener every time the status of the animation changes.
...@@ -187,10 +207,22 @@ mixin AnimationLocalStatusListenersMixin { ...@@ -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. /// Calls all the status listeners.
/// ///
/// If listeners are added or removed during this function, the modifications /// If listeners are added or removed during this function, the modifications
/// will not change which listeners are called during this iteration. /// will not change which listeners are called during this iteration.
@protected
void notifyStatusListeners(AnimationStatus status) { void notifyStatusListeners(AnimationStatus status) {
final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners); final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
for (final AnimationStatusListener listener in localListeners) { for (final AnimationStatusListener listener in localListeners) {
......
...@@ -48,6 +48,13 @@ class ObserverList<T> extends Iterable<T> { ...@@ -48,6 +48,13 @@ class ObserverList<T> extends Iterable<T> {
return _list.remove(item); return _list.remove(item);
} }
/// Removes all items from the list.
void clear() {
_isDirty = false;
_list.clear();
_set.clear();
}
@override @override
bool contains(Object? element) { bool contains(Object? element) {
if (_list.length < 3) if (_list.length < 3)
......
...@@ -416,6 +416,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> { ...@@ -416,6 +416,7 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
@override @override
void dispose() { void dispose() {
assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.'); assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
_animation?.removeStatusListener(_handleStatusChanged);
_controller?.dispose(); _controller?.dispose();
_transitionCompleter.complete(_result); _transitionCompleter.complete(_result);
super.dispose(); 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