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!.dispose();
|
||||||
_ticker = null;
|
_ticker = null;
|
||||||
|
clearStatusListeners();
|
||||||
|
clearListeners();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
|||||||
/// * [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 {
|
|||||||
/// 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 {
|
|||||||
/// [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 {
|
|||||||
///
|
///
|
||||||
/// 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
|||||||
///
|
///
|
||||||
/// 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
|||||||
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> {
|
|||||||
@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();
|
||||||
|
@ -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