Skip to content

Commit 15f5484

Browse files
[flutter_markdown] Fix some memory leaks and activate leak testing [prod-leak-fix] (#8367)
1 parent 733869c commit 15f5484

File tree

9 files changed

+86
-25
lines changed

9 files changed

+86
-25
lines changed

packages/flutter_markdown/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## NEXT
1+
## 0.7.5
22

33
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.
4+
* Fixes some memory leaks.
45

56
## 0.7.4+3
67

packages/flutter_markdown/lib/src/builder.dart

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,20 @@ class MarkdownBuilder implements md.NodeVisitor {
346346
child = builders[_blocks.last.tag!]!
347347
.visitText(text, styleSheet.styles[_blocks.last.tag!]);
348348
} else if (_blocks.last.tag == 'pre') {
349-
final ScrollController preScrollController = ScrollController();
350-
child = Scrollbar(
351-
controller: preScrollController,
352-
child: SingleChildScrollView(
353-
controller: preScrollController,
354-
scrollDirection: Axis.horizontal,
355-
padding: styleSheet.codeblockPadding,
356-
child: _buildRichText(delegate.formatText(styleSheet, text.text)),
357-
),
358-
);
349+
child = _ScrollControllerBuilder(
350+
builder: (BuildContext context, ScrollController preScrollController,
351+
Widget? child) {
352+
return Scrollbar(
353+
controller: preScrollController,
354+
child: SingleChildScrollView(
355+
controller: preScrollController,
356+
scrollDirection: Axis.horizontal,
357+
padding: styleSheet.codeblockPadding,
358+
child: child,
359+
),
360+
);
361+
},
362+
child: _buildRichText(delegate.formatText(styleSheet, text.text)));
359363
} else {
360364
child = _buildRichText(
361365
TextSpan(
@@ -448,15 +452,20 @@ class MarkdownBuilder implements md.NodeVisitor {
448452
}
449453
} else if (tag == 'table') {
450454
if (styleSheet.tableColumnWidth is FixedColumnWidth) {
451-
final ScrollController tableScrollController = ScrollController();
452-
child = Scrollbar(
453-
controller: tableScrollController,
454-
child: SingleChildScrollView(
455-
controller: tableScrollController,
456-
scrollDirection: Axis.horizontal,
457-
padding: styleSheet.tablePadding,
458-
child: _buildTable(),
459-
),
455+
child = _ScrollControllerBuilder(
456+
builder: (BuildContext context,
457+
ScrollController tableScrollController, Widget? child) {
458+
return Scrollbar(
459+
controller: tableScrollController,
460+
child: SingleChildScrollView(
461+
controller: tableScrollController,
462+
scrollDirection: Axis.horizontal,
463+
padding: styleSheet.tablePadding,
464+
child: child,
465+
),
466+
);
467+
},
468+
child: _buildTable(),
460469
);
461470
} else {
462471
child = _buildTable();
@@ -1017,3 +1026,33 @@ class MarkdownBuilder implements md.NodeVisitor {
10171026
}
10181027
}
10191028
}
1029+
1030+
class _ScrollControllerBuilder extends StatefulWidget {
1031+
const _ScrollControllerBuilder({
1032+
required this.builder,
1033+
this.child,
1034+
});
1035+
1036+
final ValueWidgetBuilder<ScrollController> builder;
1037+
1038+
final Widget? child;
1039+
1040+
@override
1041+
State<_ScrollControllerBuilder> createState() =>
1042+
_ScrollControllerBuilderState();
1043+
}
1044+
1045+
class _ScrollControllerBuilderState extends State<_ScrollControllerBuilder> {
1046+
final ScrollController _controller = ScrollController();
1047+
1048+
@override
1049+
void dispose() {
1050+
_controller.dispose();
1051+
super.dispose();
1052+
}
1053+
1054+
@override
1055+
Widget build(BuildContext context) {
1056+
return widget.builder(context, _controller, widget.child);
1057+
}
1058+
}

packages/flutter_markdown/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output,
44
formatted with simple Markdown tags.
55
repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22
7-
version: 0.7.4+3
7+
version: 0.7.5
88

99
environment:
1010
sdk: ^3.4.0
@@ -20,6 +20,7 @@ dependencies:
2020
dev_dependencies:
2121
flutter_test:
2222
sdk: flutter
23+
leak_tracker_flutter_testing: any
2324
mockito: ^5.4.4
2425
standard_message_codec: ^0.0.1+3
2526

packages/flutter_markdown/test/custom_syntax_test.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,12 @@ class WikilinkSyntax extends md.InlineSyntax {
276276
class WikilinkBuilder extends MarkdownElementBuilder {
277277
@override
278278
Widget visitElementAfter(md.Element element, _) {
279-
return Text.rich(TextSpan(
280-
text: element.textContent,
281-
recognizer: TapGestureRecognizer()..onTap = () {}));
279+
final TapGestureRecognizer recognizer = TapGestureRecognizer()
280+
..onTap = () {};
281+
addTearDown(recognizer.dispose);
282+
return Text.rich(
283+
TextSpan(text: element.textContent, recognizer: recognizer),
284+
);
282285
}
283286
}
284287

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
8+
9+
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
10+
LeakTesting.enable();
11+
LeakTracking.warnForUnsupportedPlatforms = false;
12+
await testMain();
13+
}

packages/flutter_markdown/test/image_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ void defineTests() {
455455
find.byType(Container),
456456
matchesGoldenFile(
457457
'assets/images/golden/image_test/custom_builder_asset_logo.png'));
458+
imageCache.clear();
458459
},
459460
skip: kIsWeb, // Goldens are platform-specific.
460461
);

packages/flutter_markdown/test/link_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void defineTests() {
142142
testWidgets(
143143
'multiple inline links with same content should not throw an exception',
144144
(WidgetTester tester) async {
145-
//Arange
145+
//Arrange
146146
final Widget toBePumped = boilerplate(
147147
Column(
148148
children: <Widget>[
@@ -1477,6 +1477,7 @@ void defineTests() {
14771477

14781478
gestureWidget.onTap!();
14791479
expectLinkTap(linkTapResults, const MarkdownLink('moon', '/uri'));
1480+
imageCache.clear();
14801481
},
14811482
);
14821483

packages/flutter_markdown/test/padding_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ void defineTests() {
5050
paddings[3].padding.along(Axis.horizontal) == paddingX * 4 * 2,
5151
true,
5252
);
53+
imageCache.clear();
5354
},
5455
);
5556
});

packages/flutter_markdown/test/scrollable_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ void defineTests() {
6868
final ScrollController controller = ScrollController(
6969
initialScrollOffset: 209.0,
7070
);
71+
addTearDown(controller.dispose);
7172

7273
await tester.pumpWidget(
7374
boilerplate(

0 commit comments

Comments
 (0)