diff --git a/third_party/packages/flutter_svg/CHANGELOG.md b/third_party/packages/flutter_svg/CHANGELOG.md index aa5285aae6d4..0f9dd9ee9019 100644 --- a/third_party/packages/flutter_svg/CHANGELOG.md +++ b/third_party/packages/flutter_svg/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.17 + +* Implement errorBuilder callback + ## 2.0.16 * Adopts code excerpts for README. diff --git a/third_party/packages/flutter_svg/lib/svg.dart b/third_party/packages/flutter_svg/lib/svg.dart index 0efcbdaa1eef..0cb5bb071ee5 100644 --- a/third_party/packages/flutter_svg/lib/svg.dart +++ b/third_party/packages/flutter_svg/lib/svg.dart @@ -16,6 +16,14 @@ export 'src/cache.dart'; export 'src/default_theme.dart'; export 'src/loaders.dart'; +/// Builder function to create an error widget. This builder is called when +/// the image failed loading. +typedef SvgErrorWidgetBuilder = Widget Function( + BuildContext context, + Object error, + StackTrace stackTrace, +); + /// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot] /// or [PictureInfo] from [String] or [Uint8List]. final Svg svg = Svg._(); @@ -86,6 +94,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, @Deprecated( 'No code should use this parameter. It never was implemented properly. ' 'The SVG theme must be set on the bytesLoader.') @@ -184,6 +193,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, SvgTheme? theme, ui.ColorFilter? colorFilter, @Deprecated('Use colorFilter instead.') ui.Color? color, @@ -248,6 +258,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, SvgTheme? theme, http.Client? httpClient, @@ -306,6 +317,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, SvgTheme? theme, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, }) : bytesLoader = SvgFileLoader(file, theme: theme), @@ -355,6 +367,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, SvgTheme? theme, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, }) : bytesLoader = SvgBytesLoader(bytes, theme: theme), @@ -404,6 +417,7 @@ class SvgPicture extends StatelessWidget { this.semanticsLabel, this.excludeFromSemantics = false, this.clipBehavior = Clip.hardEdge, + this.errorBuilder, SvgTheme? theme, @Deprecated('This no longer does anything.') bool cacheColorFilter = false, }) : bytesLoader = SvgStringLoader(string, theme: theme), @@ -487,6 +501,9 @@ class SvgPicture extends StatelessWidget { /// Defaults to [Clip.hardEdge], and must not be null. final Clip clipBehavior; + /// Widget displayed while the target image failed loading. + final SvgErrorWidgetBuilder? errorBuilder; + /// The color filter, if any, to apply to this widget. final ColorFilter? colorFilter; @@ -501,6 +518,7 @@ class SvgPicture extends StatelessWidget { semanticsLabel: semanticsLabel, excludeFromSemantics: excludeFromSemantics, clipBehavior: clipBehavior, + errorBuilder: errorBuilder, colorFilter: colorFilter, placeholderBuilder: placeholderBuilder, clipViewbox: !allowDrawingOutsideViewBox, diff --git a/third_party/packages/flutter_svg/pubspec.yaml b/third_party/packages/flutter_svg/pubspec.yaml index c90f9e047246..ca33c7e36d19 100644 --- a/third_party/packages/flutter_svg/pubspec.yaml +++ b/third_party/packages/flutter_svg/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_svg description: An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files. repository: https://github.com/flutter/packages/tree/main/third_party/packages/flutter_svg issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_svg%22 -version: 2.0.16 +version: 2.0.17 environment: sdk: ^3.4.0 diff --git a/third_party/packages/flutter_svg/test/widget_svg_test.dart b/third_party/packages/flutter_svg/test/widget_svg_test.dart index d857cc757700..94edb83a1f3e 100644 --- a/third_party/packages/flutter_svg/test/widget_svg_test.dart +++ b/third_party/packages/flutter_svg/test/widget_svg_test.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -767,6 +768,107 @@ void main() { widgetFinder, matchesGoldenFile('golden_widget/image_$key.png')); } }); + + group('SvgPicture - errorBuilder', () { + testWidgets('SvgPicture.string handles failure', + (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.string( + '', + errorBuilder: ( + BuildContext context, + Object error, + StackTrace stackTrace, + ) { + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('image failed'), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('image failed'), findsOneWidget); + }); + + testWidgets('SvgPicture.memory handles failure', + (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.memory( + Uint8List.fromList(utf8.encode('')), + errorBuilder: ( + BuildContext context, + Object error, + StackTrace stackTrace, + ) { + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('image failed'), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('image failed'), findsOneWidget); + }); + + testWidgets('SvgPicture.asset handles failure', + (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.asset( + '/wrong path', + errorBuilder: ( + BuildContext context, + Object error, + StackTrace stackTrace, + ) { + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('image failed'), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('image failed'), findsOneWidget); + }); + + testWidgets('SvgPicture.file handles failure', (WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: mediaQueryData, + child: SvgPicture.file( + File('nosuchfile'), + errorBuilder: ( + BuildContext context, + Object error, + StackTrace stackTrace, + ) { + return const Directionality( + textDirection: TextDirection.ltr, + child: Text('image failed'), + ); + }, + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('image failed'), findsOneWidget); + }); + }); } class FakeAssetBundle extends Fake implements AssetBundle {