Fix memory leaks in SnackBar (#147212)

This commit is contained in:
Valentin Vignal 2024-05-15 06:26:10 +08:00 committed by GitHub
parent b23f34657e
commit f335145529
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 26 deletions

View file

@ -525,10 +525,17 @@ class SnackBar extends StatefulWidget {
class _SnackBarState extends State<SnackBar> {
bool _wasVisible = false;
CurvedAnimation? _heightAnimation;
CurvedAnimation? _fadeInAnimation;
CurvedAnimation? _fadeInM3Animation;
CurvedAnimation? _fadeOutAnimation;
CurvedAnimation? _heightM3Animation;
@override
void initState() {
super.initState();
widget.animation!.addStatusListener(_onAnimationStatusChanged);
_setAnimations();
}
@override
@ -537,12 +544,47 @@ class _SnackBarState extends State<SnackBar> {
if (widget.animation != oldWidget.animation) {
oldWidget.animation!.removeStatusListener(_onAnimationStatusChanged);
widget.animation!.addStatusListener(_onAnimationStatusChanged);
_disposeAnimations();
_setAnimations();
}
}
void _setAnimations() {
assert(widget.animation != null);
_heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarHeightCurve);
_fadeInAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarFadeInCurve);
_fadeInM3Animation = CurvedAnimation(parent: widget.animation!, curve: _snackBarM3FadeInCurve);
_fadeOutAnimation = CurvedAnimation(
parent: widget.animation!,
curve: _snackBarFadeOutCurve,
reverseCurve: const Threshold(0.0),
);
// Material 3 Animation has a height animation on entry, but a direct fade out on exit.
_heightM3Animation = CurvedAnimation(
parent: widget.animation!,
curve: _snackBarM3HeightCurve,
reverseCurve: const Threshold(0.0),
);
}
void _disposeAnimations() {
_heightAnimation?.dispose();
_fadeInAnimation?.dispose();
_fadeInM3Animation?.dispose();
_fadeOutAnimation?.dispose();
_heightM3Animation?.dispose();
_heightAnimation = null;
_fadeInAnimation = null;
_fadeInM3Animation = null;
_fadeOutAnimation = null;
_heightM3Animation = null;
}
@override
void dispose() {
widget.animation!.removeStatusListener(_onAnimationStatusChanged);
_disposeAnimations();
super.dispose();
}
@ -634,21 +676,7 @@ class _SnackBarState extends State<SnackBar> {
final double actionHorizontalMargin = (widget.padding?.resolve(TextDirection.ltr).right ?? horizontalPadding) / 2;
final double iconHorizontalMargin = (widget.padding?.resolve(TextDirection.ltr).right ?? horizontalPadding) / 12.0;
final CurvedAnimation heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarHeightCurve);
final CurvedAnimation fadeInAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarFadeInCurve);
final CurvedAnimation fadeInM3Animation = CurvedAnimation(parent: widget.animation!, curve: _snackBarM3FadeInCurve);
final CurvedAnimation fadeOutAnimation = CurvedAnimation(
parent: widget.animation!,
curve: _snackBarFadeOutCurve,
reverseCurve: const Threshold(0.0),
);
// Material 3 Animation has a height animation on entry, but a direct fade out on exit.
final CurvedAnimation heightM3Animation = CurvedAnimation(
parent: widget.animation!,
curve: _snackBarM3HeightCurve,
reverseCurve: const Threshold(0.0),
);
final IconButton? iconButton = showCloseIcon
@ -758,7 +786,7 @@ class _SnackBarState extends State<SnackBar> {
child: accessibleNavigation || theme.useMaterial3
? snackBar
: FadeTransition(
opacity: fadeOutAnimation,
opacity: _fadeOutAnimation!,
child: snackBar,
),
),
@ -808,19 +836,19 @@ class _SnackBarState extends State<SnackBar> {
snackBarTransition = snackBar;
} else if (isFloatingSnackBar && !theme.useMaterial3) {
snackBarTransition = FadeTransition(
opacity: fadeInAnimation,
opacity: _fadeInAnimation!,
child: snackBar,
);
// Is Material 3 Floating Snack Bar.
} else if (isFloatingSnackBar && theme.useMaterial3) {
snackBarTransition = FadeTransition(
opacity: fadeInM3Animation,
child: AnimatedBuilder(
animation: heightM3Animation,
builder: (BuildContext context, Widget? child) {
opacity: _fadeInM3Animation!,
child: ValueListenableBuilder<double>(
valueListenable: _heightM3Animation!,
builder: (BuildContext context, double value, Widget? child) {
return Align(
alignment: Alignment.bottomLeft,
heightFactor: heightM3Animation.value,
heightFactor: value,
child: child,
);
},
@ -828,12 +856,12 @@ class _SnackBarState extends State<SnackBar> {
),
);
} else {
snackBarTransition = AnimatedBuilder(
animation: heightAnimation,
builder: (BuildContext context, Widget? child) {
snackBarTransition = ValueListenableBuilder<double>(
valueListenable: _heightAnimation!,
builder: (BuildContext context, double value, Widget? child) {
return Align(
alignment: AlignmentDirectional.topStart,
heightFactor: heightAnimation.value,
heightFactor: value,
child: child,
);
},

View file

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
void main() {
testWidgets('MaterialBanner properties are respected', (WidgetTester tester) async {
@ -507,7 +508,10 @@ void main() {
expect(find.text('banner2'), findsNothing);
});
testWidgets('ScaffoldMessenger does not duplicate a MaterialBanner when presenting a SnackBar.', (WidgetTester tester) async {
testWidgets('ScaffoldMessenger does not duplicate a MaterialBanner when presenting a SnackBar.',
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
(WidgetTester tester) async {
const Key materialBannerTapTarget = Key('materialbanner-tap-target');
const Key snackBarTapTarget = Key('snackbar-tap-target');
const String snackBarText = 'SnackBar';