fixes CupertinoFullscreenDialogTransition leaks (#147168)

This commit is contained in:
Dimil Kalathiya 2024-04-27 01:49:31 +05:30 committed by GitHub
parent de411a5976
commit cc9ac7d13c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 123 additions and 23 deletions

View File

@ -506,44 +506,96 @@ class _CupertinoPageTransitionState extends State<CupertinoPageTransition> {
/// ///
/// For example, used when creating a new calendar event by bringing in the next /// For example, used when creating a new calendar event by bringing in the next
/// screen from the bottom. /// screen from the bottom.
class CupertinoFullscreenDialogTransition extends StatelessWidget { class CupertinoFullscreenDialogTransition extends StatefulWidget {
/// Creates an iOS-style transition used for summoning fullscreen dialogs. /// Creates an iOS-style transition used for summoning fullscreen dialogs.
/// ///
const CupertinoFullscreenDialogTransition({
super.key,
required this.primaryRouteAnimation,
required this.secondaryRouteAnimation,
required this.child,
required this.linearTransition,
});
/// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
/// when this screen is being pushed. /// when this screen is being pushed.
final Animation<double> primaryRouteAnimation;
/// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
/// when another screen is being pushed on top of this one. /// when another screen is being pushed on top of this one.
/// * `linearTransition` is whether to perform the secondary transition linearly. final Animation<double> secondaryRouteAnimation;
/// * `linearTransition` is whether to perform the transitions linearly.
/// Used to precisely track back gesture drags. /// Used to precisely track back gesture drags.
CupertinoFullscreenDialogTransition({ final bool linearTransition;
super.key,
required Animation<double> primaryRouteAnimation, /// The widget below this widget in the tree.
required Animation<double> secondaryRouteAnimation, final Widget child;
required this.child,
required bool linearTransition, @override
}) : _positionAnimation = CurvedAnimation( State<CupertinoFullscreenDialogTransition> createState() => _CupertinoFullscreenDialogTransitionState();
parent: primaryRouteAnimation, }
class _CupertinoFullscreenDialogTransitionState extends State<CupertinoFullscreenDialogTransition> {
/// When this page is coming in to cover another page.
late Animation<Offset> _primaryPositionAnimation;
/// When this page is becoming covered by another page.
late Animation<Offset> _secondaryPositionAnimation;
/// Curve of primary page which is coming in to cover another page.
CurvedAnimation? _primaryPositionCurve;
/// Curve of secondary page which is becoming covered by another page.
CurvedAnimation? _secondaryPositionCurve;
@override
void initState() {
super.initState();
_setupAnimation();
}
@override
void didUpdateWidget(covariant CupertinoFullscreenDialogTransition oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation ||
oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation ||
oldWidget.child != widget.child ||
oldWidget.linearTransition != widget.linearTransition) {
_disposeCurve();
_setupAnimation();
}
}
@override
void dispose() {
_disposeCurve();
super.dispose();
}
void _disposeCurve() {
_primaryPositionCurve?.dispose();
_secondaryPositionCurve?.dispose();
_primaryPositionCurve = null;
_secondaryPositionCurve = null;
}
void _setupAnimation() {
_primaryPositionAnimation = (_primaryPositionCurve = CurvedAnimation(
parent: widget.primaryRouteAnimation,
curve: Curves.linearToEaseOut, curve: Curves.linearToEaseOut,
// The curve must be flipped so that the reverse animation doesn't play // The curve must be flipped so that the reverse animation doesn't play
// an ease-in curve, which iOS does not use. // an ease-in curve, which iOS does not use.
reverseCurve: Curves.linearToEaseOut.flipped, reverseCurve: Curves.linearToEaseOut.flipped,
).drive(_kBottomUpTween), )).drive(_kBottomUpTween);
_secondaryPositionAnimation = _secondaryPositionAnimation =
(linearTransition (widget.linearTransition
? secondaryRouteAnimation ? widget.secondaryRouteAnimation
: CurvedAnimation( : _secondaryPositionCurve = CurvedAnimation(
parent: secondaryRouteAnimation, parent: widget.secondaryRouteAnimation,
curve: Curves.linearToEaseOut, curve: Curves.linearToEaseOut,
reverseCurve: Curves.easeInToLinear, reverseCurve: Curves.easeInToLinear,
) )
).drive(_kMiddleLeftTween); ).drive(_kMiddleLeftTween);
}
final Animation<Offset> _positionAnimation;
// When this page is becoming covered by another page.
final Animation<Offset> _secondaryPositionAnimation;
/// The widget below this widget in the tree.
final Widget child;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -554,8 +606,8 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
textDirection: textDirection, textDirection: textDirection,
transformHitTests: false, transformHitTests: false,
child: SlideTransition( child: SlideTransition(
position: _positionAnimation, position: _primaryPositionAnimation,
child: child, child: widget.child,
), ),
); );
} }

View File

@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
@ -2431,6 +2432,53 @@ void main() {
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0); expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0);
}); });
}); });
testWidgets(
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: <String>['CurvedAnimation']),
'Fullscreen route does not leak CurveAnimation', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
return CupertinoButton(
child: const Text('Button'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (BuildContext context) {
return Column(
children: <Widget>[
const Placeholder(),
CupertinoButton(
child: const Text('Close'),
onPressed: () {
Navigator.pop<void>(context);
},
),
],
);
},
));
},
);
},
),
),
);
// Enter animation.
await tester.tap(find.text('Button'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
// Exit animation
await tester.tap(find.text('Close'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
});
} }
class MockNavigatorObserver extends NavigatorObserver { class MockNavigatorObserver extends NavigatorObserver {