diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index 30e4a380497..a7274b56908 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.7.5 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Fixes some memory leaks. ## 0.7.4+3 diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index 50bdd11591f..3fdd9ff13e2 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -346,16 +346,20 @@ class MarkdownBuilder implements md.NodeVisitor { child = builders[_blocks.last.tag!]! .visitText(text, styleSheet.styles[_blocks.last.tag!]); } else if (_blocks.last.tag == 'pre') { - final ScrollController preScrollController = ScrollController(); - child = Scrollbar( - controller: preScrollController, - child: SingleChildScrollView( - controller: preScrollController, - scrollDirection: Axis.horizontal, - padding: styleSheet.codeblockPadding, - child: _buildRichText(delegate.formatText(styleSheet, text.text)), - ), - ); + child = _ScrollControllerBuilder( + builder: (BuildContext context, ScrollController preScrollController, + Widget? child) { + return Scrollbar( + controller: preScrollController, + child: SingleChildScrollView( + controller: preScrollController, + scrollDirection: Axis.horizontal, + padding: styleSheet.codeblockPadding, + child: child, + ), + ); + }, + child: _buildRichText(delegate.formatText(styleSheet, text.text))); } else { child = _buildRichText( TextSpan( @@ -448,15 +452,20 @@ class MarkdownBuilder implements md.NodeVisitor { } } else if (tag == 'table') { if (styleSheet.tableColumnWidth is FixedColumnWidth) { - final ScrollController tableScrollController = ScrollController(); - child = Scrollbar( - controller: tableScrollController, - child: SingleChildScrollView( - controller: tableScrollController, - scrollDirection: Axis.horizontal, - padding: styleSheet.tablePadding, - child: _buildTable(), - ), + child = _ScrollControllerBuilder( + builder: (BuildContext context, + ScrollController tableScrollController, Widget? child) { + return Scrollbar( + controller: tableScrollController, + child: SingleChildScrollView( + controller: tableScrollController, + scrollDirection: Axis.horizontal, + padding: styleSheet.tablePadding, + child: child, + ), + ); + }, + child: _buildTable(), ); } else { child = _buildTable(); @@ -1017,3 +1026,33 @@ class MarkdownBuilder implements md.NodeVisitor { } } } + +class _ScrollControllerBuilder extends StatefulWidget { + const _ScrollControllerBuilder({ + required this.builder, + this.child, + }); + + final ValueWidgetBuilder builder; + + final Widget? child; + + @override + State<_ScrollControllerBuilder> createState() => + _ScrollControllerBuilderState(); +} + +class _ScrollControllerBuilderState extends State<_ScrollControllerBuilder> { + final ScrollController _controller = ScrollController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.builder(context, _controller, widget.child); + } +} diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index 3c8f7b6ffff..997d092ae8b 100644 --- a/packages/flutter_markdown/pubspec.yaml +++ b/packages/flutter_markdown/pubspec.yaml @@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output, formatted with simple Markdown tags. repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22 -version: 0.7.4+3 +version: 0.7.5 environment: sdk: ^3.4.0 @@ -20,6 +20,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + leak_tracker_flutter_testing: any mockito: ^5.4.4 standard_message_codec: ^0.0.1+3 diff --git a/packages/flutter_markdown/test/custom_syntax_test.dart b/packages/flutter_markdown/test/custom_syntax_test.dart index 8dc0c806e51..9af6106353e 100644 --- a/packages/flutter_markdown/test/custom_syntax_test.dart +++ b/packages/flutter_markdown/test/custom_syntax_test.dart @@ -276,9 +276,12 @@ class WikilinkSyntax extends md.InlineSyntax { class WikilinkBuilder extends MarkdownElementBuilder { @override Widget visitElementAfter(md.Element element, _) { - return Text.rich(TextSpan( - text: element.textContent, - recognizer: TapGestureRecognizer()..onTap = () {})); + final TapGestureRecognizer recognizer = TapGestureRecognizer() + ..onTap = () {}; + addTearDown(recognizer.dispose); + return Text.rich( + TextSpan(text: element.textContent, recognizer: recognizer), + ); } } diff --git a/packages/flutter_markdown/test/flutter_test_config.dart b/packages/flutter_markdown/test/flutter_test_config.dart new file mode 100644 index 00000000000..9907e578b84 --- /dev/null +++ b/packages/flutter_markdown/test/flutter_test_config.dart @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; + +Future testExecutable(FutureOr Function() testMain) async { + LeakTesting.enable(); + LeakTracking.warnForUnsupportedPlatforms = false; + await testMain(); +} diff --git a/packages/flutter_markdown/test/image_test.dart b/packages/flutter_markdown/test/image_test.dart index 07a4e87c905..77cebc10934 100644 --- a/packages/flutter_markdown/test/image_test.dart +++ b/packages/flutter_markdown/test/image_test.dart @@ -455,6 +455,7 @@ void defineTests() { find.byType(Container), matchesGoldenFile( 'assets/images/golden/image_test/custom_builder_asset_logo.png')); + imageCache.clear(); }, skip: kIsWeb, // Goldens are platform-specific. ); diff --git a/packages/flutter_markdown/test/link_test.dart b/packages/flutter_markdown/test/link_test.dart index 6e4b67c3dcb..c98fd6f3a1f 100644 --- a/packages/flutter_markdown/test/link_test.dart +++ b/packages/flutter_markdown/test/link_test.dart @@ -142,7 +142,7 @@ void defineTests() { testWidgets( 'multiple inline links with same content should not throw an exception', (WidgetTester tester) async { - //Arange + //Arrange final Widget toBePumped = boilerplate( Column( children: [ @@ -1477,6 +1477,7 @@ void defineTests() { gestureWidget.onTap!(); expectLinkTap(linkTapResults, const MarkdownLink('moon', '/uri')); + imageCache.clear(); }, ); diff --git a/packages/flutter_markdown/test/padding_test.dart b/packages/flutter_markdown/test/padding_test.dart index 6f27ca9d091..5a6f5d9ae30 100644 --- a/packages/flutter_markdown/test/padding_test.dart +++ b/packages/flutter_markdown/test/padding_test.dart @@ -50,6 +50,7 @@ void defineTests() { paddings[3].padding.along(Axis.horizontal) == paddingX * 4 * 2, true, ); + imageCache.clear(); }, ); }); diff --git a/packages/flutter_markdown/test/scrollable_test.dart b/packages/flutter_markdown/test/scrollable_test.dart index 1042a6fa98a..90f6f896729 100644 --- a/packages/flutter_markdown/test/scrollable_test.dart +++ b/packages/flutter_markdown/test/scrollable_test.dart @@ -68,6 +68,7 @@ void defineTests() { final ScrollController controller = ScrollController( initialScrollOffset: 209.0, ); + addTearDown(controller.dispose); await tester.pumpWidget( boilerplate(