mirror of
https://github.com/flutter/flutter
synced 2024-10-01 14:04:10 +00:00
Fix memory leaks in SnackBar
(#147212)
This commit is contained in:
parent
b23f34657e
commit
f335145529
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in a new issue