diff --git a/packages/vector_graphics/CHANGELOG.md b/packages/vector_graphics/CHANGELOG.md index dd9cf3fd563..6f716f9fc71 100644 --- a/packages/vector_graphics/CHANGELOG.md +++ b/packages/vector_graphics/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.16 + +* Fixes some memory leaks by disposing undisposed `ImageInfo`, `ui.Picture` and `Picture`. + ## 1.1.15 * Updates error handling in VectorGraphicWidget to handle errors when the bytes of the graphic cannot be loaded. diff --git a/packages/vector_graphics/lib/src/listener.dart b/packages/vector_graphics/lib/src/listener.dart index f266de4fb4a..6790e34843f 100644 --- a/packages/vector_graphics/lib/src/listener.dart +++ b/packages/vector_graphics/lib/src/listener.dart @@ -252,7 +252,7 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { final List<_TextConfig> _textConfig = <_TextConfig>[]; final List<_TextPosition> _textPositions = <_TextPosition>[]; final List> _pendingImages = >[]; - final Map _images = {}; + final Map _images = {}; final Map _patterns = {}; Path? _currentPath; Size _size = Size.zero; @@ -283,7 +283,7 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { try { return PictureInfo._(_recorder.endRecording(), _size); } finally { - for (final Image image in _images.values) { + for (final ImageInfo image in _images.values) { image.dispose(); } _images.clear(); @@ -746,7 +746,7 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { listener = ImageStreamListener( (ImageInfo image, bool synchronousCall) { cacheCompleter.removeListener(listener); - _images[imageId] = image.image; + _images[imageId] = image; completer.complete(); }, onError: (Object exception, StackTrace? stackTrace) { @@ -773,7 +773,7 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { @override void onDrawImage(int imageId, double x, double y, double width, double height, Float64List? transform) { - final Image image = _images[imageId]!; + final Image image = _images[imageId]!.image; if (transform != null) { _canvas.save(); _canvas.transform(transform); diff --git a/packages/vector_graphics/lib/src/render_vector_graphic.dart b/packages/vector_graphics/lib/src/render_vector_graphic.dart index a6b530469e5..9bd6bf9b564 100644 --- a/packages/vector_graphics/lib/src/render_vector_graphic.dart +++ b/packages/vector_graphics/lib/src/render_vector_graphic.dart @@ -213,6 +213,7 @@ class RenderVectorGraphic extends RenderBox { final ui.Image pending = rasterPicture.toImageSync(scaledWidth, scaledHeight); + rasterPicture.dispose(); return RasterData(pending, 0, key); } diff --git a/packages/vector_graphics/lib/src/vector_graphics.dart b/packages/vector_graphics/lib/src/vector_graphics.dart index c849a81dd09..2d8b1c6796a 100644 --- a/packages/vector_graphics/lib/src/vector_graphics.dart +++ b/packages/vector_graphics/lib/src/vector_graphics.dart @@ -340,8 +340,10 @@ class _VectorGraphicWidgetState extends State { return; } data.count -= 1; - if (data.count == 0 && _livePictureCache.containsKey(data.key)) { - _livePictureCache.remove(data.key); + if (data.count == 0) { + if (_livePictureCache.containsKey(data.key)) { + _livePictureCache.remove(data.key); + } data.pictureInfo.picture.dispose(); } } @@ -382,7 +384,7 @@ class _VectorGraphicWidgetState extends State { } Future _loadAssetBytes() async { - // First check if we have an avilable picture and use this immediately. + // First check if we have an available picture and use this immediately. final Object loaderKey = widget.loader.cacheKey(context); final _PictureKey key = _PictureKey(loaderKey, locale, textDirection, widget.clipViewbox); diff --git a/packages/vector_graphics/pubspec.yaml b/packages/vector_graphics/pubspec.yaml index 8dd286357ba..af4e4017f7f 100644 --- a/packages/vector_graphics/pubspec.yaml +++ b/packages/vector_graphics/pubspec.yaml @@ -2,7 +2,7 @@ name: vector_graphics description: A vector graphics rendering package for Flutter using a binary encoding. repository: https://github.com/flutter/packages/tree/main/packages/vector_graphics issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+vector_graphics%22 -version: 1.1.15 +version: 1.1.16 environment: sdk: ^3.4.0 @@ -17,6 +17,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + leak_tracker_flutter_testing: any vector_graphics_compiler: ^1.1.11+1 platforms: diff --git a/packages/vector_graphics/test/flutter_test_config.dart b/packages/vector_graphics/test/flutter_test_config.dart new file mode 100644 index 00000000000..9907e578b84 --- /dev/null +++ b/packages/vector_graphics/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/vector_graphics/test/vector_graphics_test.dart b/packages/vector_graphics/test/vector_graphics_test.dart index 8d8bc7fcb12..f8b034d9dba 100644 --- a/packages/vector_graphics/test/vector_graphics_test.dart +++ b/packages/vector_graphics/test/vector_graphics_test.dart @@ -442,7 +442,9 @@ void main() { ); await tester.pumpAndSettle(); - expect(await completer.future, isA()); + final PictureInfo picture = await completer.future; + addTearDown(picture.picture.dispose); + expect(picture, isA()); expect(debugLastLocale, const Locale('fr', 'CH')); expect(debugLastTextDirection, TextDirection.rtl); }); @@ -475,7 +477,9 @@ void main() { ); await tester.pumpAndSettle(); - expect(await completer.future, isA()); + final PictureInfo picture = await completer.future; + addTearDown(picture.picture.dispose); + expect(picture, isA()); expect(debugLastLocale, PlatformDispatcher.instance.locale); expect(debugLastTextDirection, TextDirection.ltr); }); @@ -584,7 +588,7 @@ void main() { expect(imageCache.statusForKey(imageKey).live, false); expect(imageCache.statusForKey(imageKey).keepAlive, true); - // A blue square, becuase the image is available now. + // A blue square, because the image is available now. await expectLater( find.byKey(key), matchesGoldenFile('vg_with_image_blue.png'),