Clear listeners when AnimationController is disposed (#79998)
This commit is contained in:
parent
97abe4418c
commit
994a19ec5f
@ -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();
|
||||
|
@ -0,0 +1,68 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user