Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
78ccced8
Unverified
Commit
78ccced8
authored
Jun 19, 2021
by
Andrew Wilson
Committed by
GitHub
Jun 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix leak of CurvedAnimations in long-lived ImplicitlyAnimatedWidgets. (#84785)
parent
05736b55
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
104 additions
and
1 deletion
+104
-1
animations.dart
packages/flutter/lib/src/animation/animations.dart
+9
-0
implicit_animations.dart
packages/flutter/lib/src/widgets/implicit_animations.dart
+4
-1
animations_test.dart
packages/flutter/test/animation/animations_test.dart
+38
-0
implicit_animations_test.dart
packages/flutter/test/widgets/implicit_animations_test.dart
+53
-0
No files found.
packages/flutter/lib/src/animation/animations.dart
View file @
78ccced8
...
...
@@ -408,6 +408,9 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
/// animation is used to animate.
AnimationStatus
?
_curveDirection
;
/// True if this CurvedAnimation has been disposed.
bool
isDisposed
=
false
;
void
_updateCurveDirection
(
AnimationStatus
status
)
{
switch
(
status
)
{
case
AnimationStatus
.
dismissed
:
...
...
@@ -427,6 +430,12 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
return
reverseCurve
==
null
||
(
_curveDirection
??
parent
.
status
)
!=
AnimationStatus
.
reverse
;
}
/// Cleans up any listeners added by this CurvedAnimation.
void
dispose
()
{
isDisposed
=
true
;
parent
.
removeStatusListener
(
_updateCurveDirection
);
}
@override
double
get
value
{
final
Curve
?
activeCurve
=
_useForwardCurve
?
curve
:
reverseCurve
;
...
...
packages/flutter/lib/src/widgets/implicit_animations.dart
View file @
78ccced8
...
...
@@ -380,8 +380,10 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
@override
void
didUpdateWidget
(
T
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
curve
!=
oldWidget
.
curve
)
if
(
widget
.
curve
!=
oldWidget
.
curve
)
{
(
_animation
as
CurvedAnimation
).
dispose
();
_animation
=
_createCurve
();
}
_controller
.
duration
=
widget
.
duration
;
if
(
_constructTweens
())
{
forEachTween
((
Tween
<
dynamic
>?
tween
,
dynamic
targetValue
,
TweenConstructor
<
dynamic
>
constructor
)
{
...
...
@@ -401,6 +403,7 @@ abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget>
@override
void
dispose
()
{
(
_animation
as
CurvedAnimation
).
dispose
();
_controller
.
dispose
();
super
.
dispose
();
}
...
...
packages/flutter/test/animation/animations_test.dart
View file @
78ccced8
...
...
@@ -302,6 +302,44 @@ FlutterError
expect
(
curved
.
value
,
moreOrLessEquals
(
0.0
));
});
test
(
'CurvedAnimation stops listening to parent when disposed.'
,
()
async
{
const
Interval
forwardCurve
=
Interval
(
0.0
,
0.5
);
const
Interval
reverseCurve
=
Interval
(
0.5
,
1.0
);
final
AnimationController
controller
=
AnimationController
(
duration:
const
Duration
(
milliseconds:
100
),
reverseDuration:
const
Duration
(
milliseconds:
100
),
vsync:
const
TestVSync
(),
);
final
CurvedAnimation
curved
=
CurvedAnimation
(
parent:
controller
,
curve:
forwardCurve
,
reverseCurve:
reverseCurve
);
expect
(
forwardCurve
.
transform
(
0.5
),
1.0
);
expect
(
reverseCurve
.
transform
(
0.5
),
0.0
);
controller
.
forward
(
from:
0.5
);
expect
(
controller
.
status
,
equals
(
AnimationStatus
.
forward
));
expect
(
curved
.
value
,
equals
(
1.0
));
controller
.
value
=
1.0
;
expect
(
controller
.
status
,
equals
(
AnimationStatus
.
completed
));
controller
.
reverse
(
from:
0.5
);
expect
(
controller
.
status
,
equals
(
AnimationStatus
.
reverse
));
expect
(
curved
.
value
,
equals
(
0.0
));
expect
(
curved
.
isDisposed
,
isFalse
);
curved
.
dispose
();
expect
(
curved
.
isDisposed
,
isTrue
);
controller
.
value
=
0.0
;
expect
(
controller
.
status
,
equals
(
AnimationStatus
.
dismissed
));
controller
.
forward
(
from:
0.5
);
expect
(
controller
.
status
,
equals
(
AnimationStatus
.
forward
));
expect
(
curved
.
value
,
equals
(
0.0
));
});
test
(
'ReverseAnimation running with different forward and reverse durations.'
,
()
{
final
AnimationController
controller
=
AnimationController
(
duration:
const
Duration
(
milliseconds:
100
),
...
...
packages/flutter/test/widgets/implicit_animations_test.dart
View file @
78ccced8
...
...
@@ -350,6 +350,59 @@ void main() {
await
tester
.
pump
(
additionalDelay
);
expect
(
mockOnEndFunction
.
called
,
1
);
});
testWidgets
(
'Ensure CurvedAnimations are disposed on widget change'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
ImplicitlyAnimatedWidgetState
<
AnimatedOpacity
>>
key
=
GlobalKey
<
ImplicitlyAnimatedWidgetState
<
AnimatedOpacity
>>();
final
ValueNotifier
<
Curve
>
curve
=
ValueNotifier
<
Curve
>(
const
Interval
(
0.0
,
0.5
));
await
tester
.
pumpWidget
(
wrap
(
child:
ValueListenableBuilder
<
Curve
>(
valueListenable:
curve
,
builder:
(
_
,
Curve
c
,
__
)
=>
AnimatedOpacity
(
key:
key
,
opacity:
1.0
,
duration:
const
Duration
(
seconds:
1
),
curve:
c
,
child:
Container
(
color:
Colors
.
green
)),
),
));
final
ImplicitlyAnimatedWidgetState
<
AnimatedOpacity
>?
firstState
=
key
.
currentState
;
final
Animation
<
double
>?
firstAnimation
=
firstState
?.
animation
;
if
(
firstAnimation
==
null
)
fail
(
'animation was null!'
);
final
CurvedAnimation
firstCurvedAnimation
=
firstAnimation
as
CurvedAnimation
;
expect
(
firstCurvedAnimation
.
isDisposed
,
isFalse
);
curve
.
value
=
const
Interval
(
0.0
,
0.6
);
await
tester
.
pumpAndSettle
();
final
ImplicitlyAnimatedWidgetState
<
AnimatedOpacity
>?
secondState
=
key
.
currentState
;
final
Animation
<
double
>?
secondAnimation
=
secondState
?.
animation
;
if
(
secondAnimation
==
null
)
fail
(
'animation was null!'
);
final
CurvedAnimation
secondCurvedAnimation
=
secondAnimation
as
CurvedAnimation
;
expect
(
firstState
,
equals
(
secondState
));
expect
(
firstAnimation
,
isNot
(
equals
(
secondAnimation
)));
expect
(
firstCurvedAnimation
.
isDisposed
,
isTrue
);
expect
(
secondCurvedAnimation
.
isDisposed
,
isFalse
);
await
tester
.
pumpWidget
(
wrap
(
child:
const
Offstage
(),
),
);
await
tester
.
pumpAndSettle
();
expect
(
secondCurvedAnimation
.
isDisposed
,
isTrue
);
});
}
Widget
wrap
(
{
required
Widget
child
})
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment