Skip to content

Commit f9d158d

Browse files
committed
feat: Error handling enhancement from flutter_svg
- copy from this PR: dnfield/flutter_svg#1104
1 parent d968c1f commit f9d158d

File tree

5 files changed

+316
-79
lines changed

5 files changed

+316
-79
lines changed
Lines changed: 1 addition & 0 deletions
Loading

third_party/packages/flutter_svg/example/lib/grid.dart

Lines changed: 103 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import 'dart:math';
2+
13
import 'package:flutter/material.dart';
24
import 'package:flutter_svg/flutter_svg.dart';
35

46
const List<String> _assetNames = <String>[
5-
// 'assets/notfound.svg', // uncomment to test an asset that doesn't exist.
7+
'assets/invalid.svg',
8+
'assets/notfound.svg', // uncomment to test an asset that doesn't exist.
69
'assets/flutter_logo.svg',
710
'assets/dart.svg',
811
'assets/simple/clip_path_3.svg',
@@ -35,7 +38,7 @@ const List<String> _assetNames = <String>[
3538
];
3639

3740
/// Assets treated as "icons" - using a color filter to render differently.
38-
const List<String> iconNames = <String>[
41+
const List<String> _iconNames = <String>[
3942
'assets/deborah_ufw/new-action-expander.svg',
4043
'assets/deborah_ufw/new-camera.svg',
4144
'assets/deborah_ufw/new-gif-button.svg',
@@ -49,12 +52,27 @@ const List<String> iconNames = <String>[
4952
];
5053

5154
/// Assets to test network access.
52-
const List<String> uriNames = <String>[
55+
const List<String> _uriNames = <String>[
5356
'http://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg',
5457
'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/410.svg',
5558
'https://upload.wikimedia.org/wikipedia/commons/b/b4/Chess_ndd45.svg',
5659
];
5760

61+
const List<String> _uriFailedNames = <String>[
62+
'an error image url.svg', // invalid url.
63+
'https: /sadf.svg', // invalid url.
64+
'http://www.google.com/404', // 404 url.
65+
'https://picsum.photos/200', // wrong format image url.
66+
];
67+
68+
const List<String> _stringNames = <String>[
69+
'''<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>''', // Shows an example of an SVG image that will fetch a raster image from a URL.
70+
'''<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle r="45" cx="50" cy="50" fill="red" /> </svg> ''', // valid svg
71+
'''<svg></svg>''', // empty svg.
72+
'sdf sdf ', // invalid svg.
73+
'', // empty string.
74+
];
75+
5876
void main() {
5977
runApp(_MyApp());
6078
}
@@ -81,59 +99,10 @@ class _MyHomePage extends StatefulWidget {
8199
}
82100

83101
class _MyHomePageState extends State<_MyHomePage> {
84-
final List<Widget> _painters = <Widget>[];
85-
late double _dimension;
86-
87-
@override
88-
void initState() {
89-
super.initState();
90-
_dimension = 203.0;
91-
for (final String assetName in _assetNames) {
92-
_painters.add(
93-
SvgPicture.asset(assetName),
94-
);
95-
}
96-
97-
for (int i = 0; i < iconNames.length; i++) {
98-
_painters.add(
99-
Directionality(
100-
textDirection: TextDirection.ltr,
101-
child: SvgPicture.asset(
102-
iconNames[i],
103-
colorFilter: ColorFilter.mode(
104-
Colors.blueGrey[(i + 1) * 100] ?? Colors.blueGrey,
105-
BlendMode.srcIn,
106-
),
107-
matchTextDirection: true,
108-
),
109-
),
110-
);
111-
}
112-
113-
for (final String uriName in uriNames) {
114-
_painters.add(
115-
SvgPicture.network(
116-
uriName,
117-
placeholderBuilder: (BuildContext context) => Container(
118-
padding: const EdgeInsets.all(30.0),
119-
child: const CircularProgressIndicator(),
120-
),
121-
),
122-
);
123-
}
124-
// Shows an example of an SVG image that will fetch a raster image from a URL.
125-
_painters.add(SvgPicture.string('''
126-
<svg viewBox="0 0 200 200"
127-
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
128-
<image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/>
129-
</svg>'''));
130-
}
102+
double _dimension = 60;
131103

132104
@override
133105
Widget build(BuildContext context) {
134-
if (_dimension > MediaQuery.of(context).size.width - 10.0) {
135-
_dimension = MediaQuery.of(context).size.width - 10.0;
136-
}
137106
return Scaffold(
138107
appBar: AppBar(
139108
title: Text(widget.title),
@@ -144,7 +113,7 @@ class _MyHomePageState extends State<_MyHomePage> {
144113
max: MediaQuery.of(context).size.width - 10.0,
145114
value: _dimension,
146115
onChanged: (double val) {
147-
setState(() => _dimension = val);
116+
setState(() => _dimension = min(MediaQuery.of(context).size.width - 10.0, val));
148117
},
149118
),
150119
Expanded(
@@ -154,7 +123,86 @@ class _MyHomePageState extends State<_MyHomePage> {
154123
padding: const EdgeInsets.all(4.0),
155124
mainAxisSpacing: 4.0,
156125
crossAxisSpacing: 4.0,
157-
children: _painters.toList(),
126+
children: <Widget>[
127+
..._assetNames.map(
128+
(String e) => SvgPicture.asset(
129+
e,
130+
placeholderBuilder: (BuildContext context) => Container(
131+
padding: const EdgeInsets.all(30.0),
132+
child: const CircularProgressIndicator(),
133+
),
134+
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
135+
color: Colors.brown,
136+
width: 10,
137+
height: 10,
138+
),
139+
),
140+
),
141+
..._iconNames.map(
142+
(String e) => Directionality(
143+
textDirection: TextDirection.ltr,
144+
child: SvgPicture.asset(
145+
e,
146+
colorFilter: ColorFilter.mode(
147+
Colors.blueGrey[(_iconNames.indexOf(e) + 1) * 100] ?? Colors.blueGrey,
148+
BlendMode.srcIn,
149+
),
150+
matchTextDirection: true,
151+
placeholderBuilder: (BuildContext context) => Container(
152+
padding: const EdgeInsets.all(30.0),
153+
child: const CircularProgressIndicator(),
154+
),
155+
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
156+
color: Colors.yellow,
157+
width: 10,
158+
height: 10,
159+
),
160+
),
161+
),
162+
),
163+
..._uriNames.map(
164+
(String e) => SvgPicture.network(
165+
e,
166+
placeholderBuilder: (BuildContext context) => Container(
167+
padding: const EdgeInsets.all(30.0),
168+
child: const CircularProgressIndicator(),
169+
),
170+
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
171+
color: Colors.red,
172+
width: 10,
173+
height: 10,
174+
),
175+
),
176+
),
177+
..._uriFailedNames.map(
178+
(String e) => SvgPicture.network(
179+
e,
180+
placeholderBuilder: (BuildContext context) => Container(
181+
padding: const EdgeInsets.all(30.0),
182+
child: const CircularProgressIndicator(),
183+
),
184+
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
185+
color: Colors.deepPurple,
186+
width: 10,
187+
height: 10,
188+
),
189+
),
190+
),
191+
..._stringNames.map(
192+
(String e) => SvgPicture.string(
193+
e,
194+
placeholderBuilder: (BuildContext context) => Container(
195+
padding: const EdgeInsets.all(30.0),
196+
child: const CircularProgressIndicator(),
197+
),
198+
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
199+
color: Colors.pinkAccent,
200+
width: 10,
201+
height: 10,
202+
),
203+
),
204+
),
205+
],
158206
),
159207
),
160208
]),

third_party/packages/flutter_svg/lib/src/loaders.dart

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,20 +152,29 @@ abstract class SvgLoader<T> extends BytesLoader {
152152
final SvgTheme theme = getTheme(context);
153153
return prepareMessage(context).then((T? message) {
154154
return compute((T? message) {
155-
return vg
156-
.encodeSvg(
157-
xml: provideSvg(message),
158-
theme: theme.toVgTheme(),
159-
colorMapper: colorMapper == null
160-
? null
161-
: _DelegateVgColorMapper(colorMapper!),
162-
debugName: 'Svg loader',
163-
enableClippingOptimizer: false,
164-
enableMaskingOptimizer: false,
165-
enableOverdrawOptimizer: false,
166-
)
167-
.buffer
168-
.asByteData();
155+
try {
156+
debugPrint('SvgLoader._load.provideSvg: empty');
157+
final String xml = provideSvg(message);
158+
if (xml.isEmpty) {
159+
return Future<ByteData>.value(ByteData(0));
160+
} else {
161+
return vg
162+
.encodeSvg(
163+
xml: xml,
164+
theme: theme.toVgTheme(),
165+
colorMapper: colorMapper == null ? null : _DelegateVgColorMapper(colorMapper!),
166+
debugName: 'Svg loader',
167+
enableClippingOptimizer: false,
168+
enableMaskingOptimizer: false,
169+
enableOverdrawOptimizer: false,
170+
)
171+
.buffer
172+
.asByteData();
173+
}
174+
} catch (e) {
175+
debugPrint('SvgLoader._load.error: $e');
176+
return Future<ByteData>.value(ByteData(0));
177+
}
169178
}, message, debugLabel: 'Load Bytes');
170179
});
171180
}
@@ -373,15 +382,19 @@ class SvgAssetLoader extends SvgLoader<ByteData> {
373382
}
374383

375384
@override
376-
Future<ByteData?> prepareMessage(BuildContext? context) {
377-
return _resolveBundle(context).load(
378-
packageName == null ? assetName : 'packages/$packageName/$assetName',
379-
);
385+
Future<ByteData?> prepareMessage(BuildContext? context) async {
386+
try {
387+
return await _resolveBundle(context).load(
388+
packageName == null ? assetName : 'packages/$packageName/$assetName',
389+
);
390+
} catch (e) {
391+
debugPrint('SvgAssetLoader.prepareMessage.error: $e');
392+
return Future<ByteData?>.value();
393+
}
380394
}
381395

382396
@override
383-
String provideSvg(ByteData? message) =>
384-
utf8.decode(message!.buffer.asUint8List(), allowMalformed: true);
397+
String provideSvg(ByteData? message) => utf8.decode(message!.buffer.asUint8List(), allowMalformed: true);
385398

386399
@override
387400
SvgCacheKey cacheKey(BuildContext? context) {
@@ -437,13 +450,18 @@ class SvgNetworkLoader extends SvgLoader<Uint8List> {
437450

438451
@override
439452
Future<Uint8List?> prepareMessage(BuildContext? context) async {
440-
final http.Client client = _httpClient ?? http.Client();
441-
return (await client.get(Uri.parse(url), headers: headers)).bodyBytes;
453+
try {
454+
final http.Client client = _httpClient ?? http.Client();
455+
final http.Response res = await client.get(Uri.parse(url), headers: headers);
456+
return res.bodyBytes;
457+
} catch (e) {
458+
debugPrint('SvgNetworkLoader.prepareMessage.error: $e');
459+
return null;
460+
}
442461
}
443462

444463
@override
445-
String provideSvg(Uint8List? message) =>
446-
utf8.decode(message!, allowMalformed: true);
464+
String provideSvg(Uint8List? message) => message == null ? '' : utf8.decode(message, allowMalformed: true);
447465

448466
@override
449467
int get hashCode => Object.hash(url, headers, theme, colorMapper);

third_party/packages/flutter_svg/lib/svg.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ class SvgPicture extends StatelessWidget {
8686
this.semanticsLabel,
8787
this.excludeFromSemantics = false,
8888
this.clipBehavior = Clip.hardEdge,
89+
this.errorBuilder,
8990
@Deprecated(
9091
'No code should use this parameter. It never was implemented properly. '
9192
'The SVG theme must be set on the bytesLoader.')
9293
SvgTheme? theme,
9394
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
9495
});
9596

97+
9698
/// Instantiates a widget that renders an SVG picture from an [AssetBundle].
9799
///
98100
/// The key will be derived from the `assetName`, `package`, and `bundle`
@@ -190,6 +192,7 @@ class SvgPicture extends StatelessWidget {
190192
@Deprecated('Use colorFilter instead.')
191193
ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
192194
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
195+
this.errorBuilder,
193196
}) : bytesLoader = SvgAssetLoader(
194197
assetName,
195198
packageName: package,
@@ -251,6 +254,7 @@ class SvgPicture extends StatelessWidget {
251254
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
252255
SvgTheme? theme,
253256
http.Client? httpClient,
257+
this.errorBuilder,
254258
}) : bytesLoader = SvgNetworkLoader(
255259
url,
256260
headers: headers,
@@ -308,6 +312,7 @@ class SvgPicture extends StatelessWidget {
308312
this.clipBehavior = Clip.hardEdge,
309313
SvgTheme? theme,
310314
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
315+
this.errorBuilder,
311316
}) : bytesLoader = SvgFileLoader(file, theme: theme),
312317
colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);
313318

@@ -357,6 +362,7 @@ class SvgPicture extends StatelessWidget {
357362
this.clipBehavior = Clip.hardEdge,
358363
SvgTheme? theme,
359364
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
365+
this.errorBuilder,
360366
}) : bytesLoader = SvgBytesLoader(bytes, theme: theme),
361367
colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);
362368

@@ -406,6 +412,7 @@ class SvgPicture extends StatelessWidget {
406412
this.clipBehavior = Clip.hardEdge,
407413
SvgTheme? theme,
408414
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
415+
this.errorBuilder,
409416
}) : bytesLoader = SvgStringLoader(string, theme: theme),
410417
colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);
411418

@@ -490,6 +497,9 @@ class SvgPicture extends StatelessWidget {
490497
/// The color filter, if any, to apply to this widget.
491498
final ColorFilter? colorFilter;
492499

500+
/// The widget to show when failed to fetch, decode, and parse the SVG data.
501+
final SvgPictureErrorWidgetBuilder? errorBuilder;
502+
493503
@override
494504
Widget build(BuildContext context) {
495505
return createCompatVectorGraphic(
@@ -505,6 +515,7 @@ class SvgPicture extends StatelessWidget {
505515
placeholderBuilder: placeholderBuilder,
506516
clipViewbox: !allowDrawingOutsideViewBox,
507517
matchTextDirection: matchTextDirection,
518+
errorBuilder: errorBuilder,
508519
);
509520
}
510521

@@ -567,3 +578,10 @@ class SvgPicture extends StatelessWidget {
567578
));
568579
}
569580
}
581+
582+
/// The signature that [VectorGraphic.errorBuilder] uses to report exceptions.
583+
typedef SvgPictureErrorWidgetBuilder = Widget Function(
584+
BuildContext context,
585+
Object error,
586+
StackTrace stackTrace,
587+
);

0 commit comments

Comments
 (0)