Skip to content

Commit c6caf68

Browse files
authored
[flutter_svg] Implement errorBuilder callback (flutter#8364)
closes flutter#158612 ## Description - Implemented errorBuilder callback to handle loading failures
1 parent 4efc3d1 commit c6caf68

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

third_party/packages/flutter_svg/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.0.17
2+
3+
* Implement errorBuilder callback
4+
15
## 2.0.16
26

37
* Adopts code excerpts for README.

third_party/packages/flutter_svg/lib/svg.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ export 'src/cache.dart';
1616
export 'src/default_theme.dart';
1717
export 'src/loaders.dart';
1818

19+
/// Builder function to create an error widget. This builder is called when
20+
/// the image failed loading.
21+
typedef SvgErrorWidgetBuilder = Widget Function(
22+
BuildContext context,
23+
Object error,
24+
StackTrace stackTrace,
25+
);
26+
1927
/// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot]
2028
/// or [PictureInfo] from [String] or [Uint8List].
2129
final Svg svg = Svg._();
@@ -86,6 +94,7 @@ class SvgPicture extends StatelessWidget {
8694
this.semanticsLabel,
8795
this.excludeFromSemantics = false,
8896
this.clipBehavior = Clip.hardEdge,
97+
this.errorBuilder,
8998
@Deprecated(
9099
'No code should use this parameter. It never was implemented properly. '
91100
'The SVG theme must be set on the bytesLoader.')
@@ -184,6 +193,7 @@ class SvgPicture extends StatelessWidget {
184193
this.semanticsLabel,
185194
this.excludeFromSemantics = false,
186195
this.clipBehavior = Clip.hardEdge,
196+
this.errorBuilder,
187197
SvgTheme? theme,
188198
ui.ColorFilter? colorFilter,
189199
@Deprecated('Use colorFilter instead.') ui.Color? color,
@@ -248,6 +258,7 @@ class SvgPicture extends StatelessWidget {
248258
this.semanticsLabel,
249259
this.excludeFromSemantics = false,
250260
this.clipBehavior = Clip.hardEdge,
261+
this.errorBuilder,
251262
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
252263
SvgTheme? theme,
253264
http.Client? httpClient,
@@ -306,6 +317,7 @@ class SvgPicture extends StatelessWidget {
306317
this.semanticsLabel,
307318
this.excludeFromSemantics = false,
308319
this.clipBehavior = Clip.hardEdge,
320+
this.errorBuilder,
309321
SvgTheme? theme,
310322
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
311323
}) : bytesLoader = SvgFileLoader(file, theme: theme),
@@ -355,6 +367,7 @@ class SvgPicture extends StatelessWidget {
355367
this.semanticsLabel,
356368
this.excludeFromSemantics = false,
357369
this.clipBehavior = Clip.hardEdge,
370+
this.errorBuilder,
358371
SvgTheme? theme,
359372
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
360373
}) : bytesLoader = SvgBytesLoader(bytes, theme: theme),
@@ -404,6 +417,7 @@ class SvgPicture extends StatelessWidget {
404417
this.semanticsLabel,
405418
this.excludeFromSemantics = false,
406419
this.clipBehavior = Clip.hardEdge,
420+
this.errorBuilder,
407421
SvgTheme? theme,
408422
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
409423
}) : bytesLoader = SvgStringLoader(string, theme: theme),
@@ -487,6 +501,9 @@ class SvgPicture extends StatelessWidget {
487501
/// Defaults to [Clip.hardEdge], and must not be null.
488502
final Clip clipBehavior;
489503

504+
/// Widget displayed while the target image failed loading.
505+
final SvgErrorWidgetBuilder? errorBuilder;
506+
490507
/// The color filter, if any, to apply to this widget.
491508
final ColorFilter? colorFilter;
492509

@@ -501,6 +518,7 @@ class SvgPicture extends StatelessWidget {
501518
semanticsLabel: semanticsLabel,
502519
excludeFromSemantics: excludeFromSemantics,
503520
clipBehavior: clipBehavior,
521+
errorBuilder: errorBuilder,
504522
colorFilter: colorFilter,
505523
placeholderBuilder: placeholderBuilder,
506524
clipViewbox: !allowDrawingOutsideViewBox,

third_party/packages/flutter_svg/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: flutter_svg
22
description: An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files.
33
repository: https://github.com/flutter/packages/tree/main/third_party/packages/flutter_svg
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_svg%22
5-
version: 2.0.16
5+
version: 2.0.17
66

77
environment:
88
sdk: ^3.4.0

third_party/packages/flutter_svg/test/widget_svg_test.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
import 'dart:io';
23

34
import 'package:flutter/foundation.dart';
45
import 'package:flutter/widgets.dart';
@@ -767,6 +768,107 @@ void main() {
767768
widgetFinder, matchesGoldenFile('golden_widget/image_$key.png'));
768769
}
769770
});
771+
772+
group('SvgPicture - errorBuilder', () {
773+
testWidgets('SvgPicture.string handles failure',
774+
(WidgetTester tester) async {
775+
await tester.pumpWidget(
776+
MediaQuery(
777+
data: mediaQueryData,
778+
child: SvgPicture.string(
779+
'<!-- invalid svg -->',
780+
errorBuilder: (
781+
BuildContext context,
782+
Object error,
783+
StackTrace stackTrace,
784+
) {
785+
return const Directionality(
786+
textDirection: TextDirection.ltr,
787+
child: Text('image failed'),
788+
);
789+
},
790+
),
791+
),
792+
);
793+
await tester.pumpAndSettle();
794+
795+
expect(find.text('image failed'), findsOneWidget);
796+
});
797+
798+
testWidgets('SvgPicture.memory handles failure',
799+
(WidgetTester tester) async {
800+
await tester.pumpWidget(
801+
MediaQuery(
802+
data: mediaQueryData,
803+
child: SvgPicture.memory(
804+
Uint8List.fromList(utf8.encode('<!-- invalid svg -->')),
805+
errorBuilder: (
806+
BuildContext context,
807+
Object error,
808+
StackTrace stackTrace,
809+
) {
810+
return const Directionality(
811+
textDirection: TextDirection.ltr,
812+
child: Text('image failed'),
813+
);
814+
},
815+
),
816+
),
817+
);
818+
await tester.pumpAndSettle();
819+
820+
expect(find.text('image failed'), findsOneWidget);
821+
});
822+
823+
testWidgets('SvgPicture.asset handles failure',
824+
(WidgetTester tester) async {
825+
await tester.pumpWidget(
826+
MediaQuery(
827+
data: mediaQueryData,
828+
child: SvgPicture.asset(
829+
'/wrong path',
830+
errorBuilder: (
831+
BuildContext context,
832+
Object error,
833+
StackTrace stackTrace,
834+
) {
835+
return const Directionality(
836+
textDirection: TextDirection.ltr,
837+
child: Text('image failed'),
838+
);
839+
},
840+
),
841+
),
842+
);
843+
await tester.pumpAndSettle();
844+
845+
expect(find.text('image failed'), findsOneWidget);
846+
});
847+
848+
testWidgets('SvgPicture.file handles failure', (WidgetTester tester) async {
849+
await tester.pumpWidget(
850+
MediaQuery(
851+
data: mediaQueryData,
852+
child: SvgPicture.file(
853+
File('nosuchfile'),
854+
errorBuilder: (
855+
BuildContext context,
856+
Object error,
857+
StackTrace stackTrace,
858+
) {
859+
return const Directionality(
860+
textDirection: TextDirection.ltr,
861+
child: Text('image failed'),
862+
);
863+
},
864+
),
865+
),
866+
);
867+
await tester.pumpAndSettle();
868+
869+
expect(find.text('image failed'), findsOneWidget);
870+
});
871+
});
770872
}
771873

772874
class FakeAssetBundle extends Fake implements AssetBundle {

0 commit comments

Comments
 (0)