Skip to content

Commit c1cafcc

Browse files
fixes SearchAnchor leak (#147652)
1 parent c0be3a3 commit c1cafcc

File tree

2 files changed

+57
-25
lines changed

2 files changed

+57
-25
lines changed

packages/flutter/lib/src/material/search_anchor.dart

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
518518
final CapturedThemes capturedThemes;
519519
final TextInputAction? textInputAction;
520520
final TextInputType? keyboardType;
521+
CurvedAnimation? curvedAnimation;
522+
CurvedAnimation? viewFadeOnIntervalCurve;
521523

522524
@override
523525
Color? get barrierColor => Colors.transparent;
@@ -561,6 +563,13 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
561563
return super.didPop(result);
562564
}
563565

566+
@override
567+
void dispose() {
568+
curvedAnimation?.dispose();
569+
viewFadeOnIntervalCurve?.dispose();
570+
super.dispose();
571+
}
572+
564573
void updateViewConfig(BuildContext context) {
565574
viewDefaults = _SearchViewDefaultsM3(context, isFullScreen: showFullScreenView);
566575
viewTheme = SearchViewTheme.of(context);
@@ -620,23 +629,25 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
620629
child: AnimatedBuilder(
621630
animation: animation,
622631
builder: (BuildContext context, Widget? child) {
623-
final Animation<double> curvedAnimation = CurvedAnimation(
632+
curvedAnimation ??= CurvedAnimation(
624633
parent: animation,
625634
curve: Curves.easeInOutCubicEmphasized,
626635
reverseCurve: Curves.easeInOutCubicEmphasized.flipped,
627636
);
628637

629-
final Rect viewRect = _rectTween.evaluate(curvedAnimation)!;
638+
final Rect viewRect = _rectTween.evaluate(curvedAnimation!)!;
630639
final double topPadding = showFullScreenView
631-
? lerpDouble(0.0, MediaQuery.paddingOf(context).top, curvedAnimation.value)!
640+
? lerpDouble(0.0, MediaQuery.paddingOf(context).top, curvedAnimation!.value)!
632641
: 0.0;
633642

643+
viewFadeOnIntervalCurve ??= CurvedAnimation(
644+
parent: animation,
645+
curve: _kViewFadeOnInterval,
646+
reverseCurve: _kViewFadeOnInterval.flipped,
647+
);
648+
634649
return FadeTransition(
635-
opacity: CurvedAnimation(
636-
parent: animation,
637-
curve: _kViewFadeOnInterval,
638-
reverseCurve: _kViewFadeOnInterval.flipped,
639-
),
650+
opacity: viewFadeOnIntervalCurve!,
640651
child: capturedThemes.wrap(
641652
_ViewContent(
642653
viewOnChanged: viewOnChanged,
@@ -654,7 +665,7 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> {
654665
viewHeaderHintStyle: viewHeaderHintStyle,
655666
dividerColor: dividerColor,
656667
showFullScreenView: showFullScreenView,
657-
animation: curvedAnimation,
668+
animation: curvedAnimation!,
658669
topPadding: topPadding,
659670
viewMaxWidth: _rectTween.end!.width,
660671
viewRect: viewRect,
@@ -738,6 +749,9 @@ class _ViewContent extends StatefulWidget {
738749
class _ViewContentState extends State<_ViewContent> {
739750
Size? _screenSize;
740751
late Rect _viewRect;
752+
late CurvedAnimation viewIconsFadeCurve;
753+
late CurvedAnimation viewDividerFadeCurve;
754+
late CurvedAnimation viewListFadeOnIntervalCurve;
741755
late final SearchController _controller;
742756
Iterable<Widget> result = <Widget>[];
743757
String? searchValue;
@@ -749,6 +763,7 @@ class _ViewContentState extends State<_ViewContent> {
749763
_viewRect = widget.viewRect;
750764
_controller = widget.searchController;
751765
_controller.addListener(updateSuggestions);
766+
_setupAnimations();
752767
}
753768

754769
@override
@@ -787,11 +802,36 @@ class _ViewContentState extends State<_ViewContent> {
787802
@override
788803
void dispose() {
789804
_controller.removeListener(updateSuggestions);
805+
_disposeAnimations();
790806
_timer?.cancel();
791807
_timer = null;
792808
super.dispose();
793809
}
794810

811+
void _setupAnimations() {
812+
viewIconsFadeCurve = CurvedAnimation(
813+
parent: widget.animation,
814+
curve: _kViewIconsFadeOnInterval,
815+
reverseCurve: _kViewIconsFadeOnInterval.flipped,
816+
);
817+
viewDividerFadeCurve = CurvedAnimation(
818+
parent: widget.animation,
819+
curve: _kViewDividerFadeOnInterval,
820+
reverseCurve: _kViewFadeOnInterval.flipped,
821+
);
822+
viewListFadeOnIntervalCurve = CurvedAnimation(
823+
parent: widget.animation,
824+
curve: _kViewListFadeOnInterval,
825+
reverseCurve: _kViewListFadeOnInterval.flipped,
826+
);
827+
}
828+
829+
void _disposeAnimations() {
830+
viewIconsFadeCurve.dispose();
831+
viewDividerFadeCurve.dispose();
832+
viewListFadeOnIntervalCurve.dispose();
833+
}
834+
795835
Widget viewBuilder(Iterable<Widget> suggestions) {
796836
if (widget.viewBuilder == null) {
797837
return MediaQuery.removePadding(
@@ -900,11 +940,7 @@ class _ViewContentState extends State<_ViewContent> {
900940
maxWidth: math.min(widget.viewMaxWidth, _screenSize!.width),
901941
minWidth: 0,
902942
child: FadeTransition(
903-
opacity: CurvedAnimation(
904-
parent: widget.animation,
905-
curve: _kViewIconsFadeOnInterval,
906-
reverseCurve: _kViewIconsFadeOnInterval.flipped,
907-
),
943+
opacity: viewIconsFadeCurve,
908944
child: Column(
909945
crossAxisAlignment: CrossAxisAlignment.stretch,
910946
children: <Widget>[
@@ -937,19 +973,11 @@ class _ViewContentState extends State<_ViewContent> {
937973
),
938974
),
939975
FadeTransition(
940-
opacity: CurvedAnimation(
941-
parent: widget.animation,
942-
curve: _kViewDividerFadeOnInterval,
943-
reverseCurve: _kViewFadeOnInterval.flipped,
944-
),
976+
opacity: viewDividerFadeCurve,
945977
child: viewDivider),
946978
Expanded(
947979
child: FadeTransition(
948-
opacity: CurvedAnimation(
949-
parent: widget.animation,
950-
curve: _kViewListFadeOnInterval,
951-
reverseCurve: _kViewListFadeOnInterval.flipped,
952-
),
980+
opacity: viewListFadeOnIntervalCurve,
953981
child: viewBuilder(result),
954982
),
955983
),

packages/flutter/test/material/search_anchor_test.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart';
99
import 'package:flutter/material.dart';
1010
import 'package:flutter/rendering.dart';
1111
import 'package:flutter_test/flutter_test.dart';
12+
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
1213

1314
import '../widgets/semantics_tester.dart';
1415

@@ -898,7 +899,10 @@ void main() {
898899
expect(textField.textCapitalization, TextCapitalization.none);
899900
});
900901

901-
testWidgets('SearchAnchor respects viewOnChanged and viewOnSubmitted properties', (WidgetTester tester) async {
902+
testWidgets('SearchAnchor respects viewOnChanged and viewOnSubmitted properties',
903+
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
904+
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
905+
(WidgetTester tester) async {
902906
final SearchController controller = SearchController();
903907
addTearDown(controller.dispose);
904908
int onChangedCalled = 0;

0 commit comments

Comments
 (0)