diff --git a/packages/vector_graphics/lib/src/vector_graphics.dart b/packages/vector_graphics/lib/src/vector_graphics.dart index 290e8a78..68c84911 100644 --- a/packages/vector_graphics/lib/src/vector_graphics.dart +++ b/packages/vector_graphics/lib/src/vector_graphics.dart @@ -7,12 +7,11 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; - import 'package:vector_graphics_codec/vector_graphics_codec.dart'; import 'html_render_vector_graphics.dart'; -import 'loader.dart'; import 'listener.dart'; +import 'loader.dart'; import 'render_object_selection.dart'; import 'render_vector_graphic.dart'; @@ -309,8 +308,8 @@ class _VectorGraphicWidgetState extends State { static final Map<_PictureKey, _PictureData> _livePictureCache = <_PictureKey, _PictureData>{}; - static final Map<_PictureKey, Future<_PictureData>> _pendingPictures = - <_PictureKey, Future<_PictureData>>{}; + static final Map<_PictureKey, Future<_PictureData?>> _pendingPictures = + <_PictureKey, Future<_PictureData?>>{}; @override void didChangeDependencies() { @@ -346,12 +345,13 @@ class _VectorGraphicWidgetState extends State { } } - Future<_PictureData> _loadPicture( + Future<_PictureData?> _loadPicture( BuildContext context, _PictureKey key, BytesLoader loader) { if (_pendingPictures.containsKey(key)) { return _pendingPictures[key]!; } - final Future<_PictureData> result = + + final Future<_PictureData?> result = loader.loadBytes(context).then((ByteData data) { return decodeVectorGraphics( data, @@ -360,19 +360,25 @@ class _VectorGraphicWidgetState extends State { clipViewbox: key.clipViewbox, loader: loader, onError: (Object error, StackTrace? stackTrace) { - return _handleError( - error, - stackTrace, - ); + _handleError(error, stackTrace); }, ); - }).then((PictureInfo pictureInfo) { + }).then<_PictureData?>((PictureInfo pictureInfo) { return _PictureData(pictureInfo, 0, key); + }).catchError((Object error, StackTrace stackTrace) { + if (widget.errorBuilder != null) { + _handleError(error, stackTrace); + return null; + } else { + // ignore: only_throw_errors + throw error; + } }); _pendingPictures[key] = result; result.whenComplete(() { _pendingPictures.remove(key); }); + return result; } @@ -384,7 +390,7 @@ class _VectorGraphicWidgetState extends State { } void _loadAssetBytes() { - // 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); @@ -399,7 +405,12 @@ class _VectorGraphicWidgetState extends State { } // If not, then check if there is a pending load. final BytesLoader loader = widget.loader; - _loadPicture(context, key, loader).then((_PictureData data) { + _loadPicture(context, key, loader).then((_PictureData? data) { + if (data == null) { + // If data is null, an error occurred, and it has been handled. + return; + } + data.count += 1; // The widget may have changed, requesting a new vector graphic before diff --git a/packages/vector_graphics/test/vector_graphics_test.dart b/packages/vector_graphics/test/vector_graphics_test.dart index 8aca00a6..002c477e 100644 --- a/packages/vector_graphics/test/vector_graphics_test.dart +++ b/packages/vector_graphics/test/vector_graphics_test.dart @@ -653,6 +653,31 @@ void main() { expect(matrix.row0.x, -1); expect(matrix.row1.y, 1); }); + + testWidgets('Displays errorBuilder on loadBytes failure', (WidgetTester tester) async { + const ErrorBytesLoader loader = ErrorBytesLoader(); + final GlobalKey key = GlobalKey(); + + // Build the VectorGraphic widget with an errorBuilder + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: VectorGraphic( + key: key, + loader: loader, + width: 100, + height: 100, + errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) { + return const Text('Error occurred'); + }, + ), + )); + + // Let the widget rebuild and show the error + await tester.pumpAndSettle(); + + // Expect that the error widget is displayed + expect(find.text('Error occurred'), findsOneWidget); + }); } class TestBundle extends Fake implements AssetBundle { @@ -719,3 +744,23 @@ class TestBytesLoader extends BytesLoader { @override String toString() => 'TestBytesLoader: $source'; } + +class ErrorBytesLoader extends BytesLoader { + const ErrorBytesLoader(); + + @override + Future loadBytes(BuildContext? context) async { + throw StateError('Load failure'); + } + + @override + int get hashCode => 0; + + @override + bool operator ==(Object other) { + return other is ErrorBytesLoader; + } + + @override + String toString() => 'ErrorBytesLoader'; +}