Skip to content

Commit

Permalink
feat: Error handling enhancement from flutter_svg
Browse files Browse the repository at this point in the history
- copy from this PR: dnfield/flutter_svg#1104
  • Loading branch information
minhhung2556 committed Nov 12, 2024
1 parent 5e90ce2 commit 4763502
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 79 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 103 additions & 55 deletions third_party/packages/flutter_svg/example/lib/grid.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

const List<String> _assetNames = <String>[
// 'assets/notfound.svg', // uncomment to test an asset that doesn't exist.
'assets/invalid.svg',
'assets/notfound.svg', // uncomment to test an asset that doesn't exist.
'assets/flutter_logo.svg',
'assets/dart.svg',
'assets/simple/clip_path_3.svg',
Expand Down Expand Up @@ -35,7 +38,7 @@ const List<String> _assetNames = <String>[
];

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

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

const List<String> _uriFailedNames = <String>[
'an error image url.svg', // invalid url.
'https: /sadf.svg', // invalid url.
'http://www.google.com/404', // 404 url.
'https://picsum.photos/200', // wrong format image url.
];

const List<String> _stringNames = <String>[
'''<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.
'''<svg height="100" width="100" xmlns="http://www.w3.org/2000/svg"> <circle r="45" cx="50" cy="50" fill="red" /> </svg> ''', // valid svg
'''<svg></svg>''', // empty svg.
'sdf sdf ', // invalid svg.
'', // empty string.
];

void main() {
runApp(_MyApp());
}
Expand All @@ -81,59 +99,10 @@ class _MyHomePage extends StatefulWidget {
}

class _MyHomePageState extends State<_MyHomePage> {
final List<Widget> _painters = <Widget>[];
late double _dimension;

@override
void initState() {
super.initState();
_dimension = 203.0;
for (final String assetName in _assetNames) {
_painters.add(
SvgPicture.asset(assetName),
);
}

for (int i = 0; i < iconNames.length; i++) {
_painters.add(
Directionality(
textDirection: TextDirection.ltr,
child: SvgPicture.asset(
iconNames[i],
colorFilter: ColorFilter.mode(
Colors.blueGrey[(i + 1) * 100] ?? Colors.blueGrey,
BlendMode.srcIn,
),
matchTextDirection: true,
),
),
);
}

for (final String uriName in uriNames) {
_painters.add(
SvgPicture.network(
uriName,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
),
);
}
// Shows an example of an SVG image that will fetch a raster image from a URL.
_painters.add(SvgPicture.string('''
<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>'''));
}
double _dimension = 60;

@override
Widget build(BuildContext context) {
if (_dimension > MediaQuery.of(context).size.width - 10.0) {
_dimension = MediaQuery.of(context).size.width - 10.0;
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
Expand All @@ -144,7 +113,7 @@ class _MyHomePageState extends State<_MyHomePage> {
max: MediaQuery.of(context).size.width - 10.0,
value: _dimension,
onChanged: (double val) {
setState(() => _dimension = val);
setState(() => _dimension = min(MediaQuery.of(context).size.width - 10.0, val));
},
),
Expanded(
Expand All @@ -154,7 +123,86 @@ class _MyHomePageState extends State<_MyHomePage> {
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: _painters.toList(),
children: <Widget>[
..._assetNames.map(
(String e) => SvgPicture.asset(
e,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
color: Colors.brown,
width: 10,
height: 10,
),
),
),
..._iconNames.map(
(String e) => Directionality(
textDirection: TextDirection.ltr,
child: SvgPicture.asset(
e,
colorFilter: ColorFilter.mode(
Colors.blueGrey[(_iconNames.indexOf(e) + 1) * 100] ?? Colors.blueGrey,
BlendMode.srcIn,
),
matchTextDirection: true,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
color: Colors.yellow,
width: 10,
height: 10,
),
),
),
),
..._uriNames.map(
(String e) => SvgPicture.network(
e,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
color: Colors.red,
width: 10,
height: 10,
),
),
),
..._uriFailedNames.map(
(String e) => SvgPicture.network(
e,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
color: Colors.deepPurple,
width: 10,
height: 10,
),
),
),
..._stringNames.map(
(String e) => SvgPicture.string(
e,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator(),
),
errorBuilder: (BuildContext context, Object error, StackTrace stackTrace) => Container(
color: Colors.pinkAccent,
width: 10,
height: 10,
),
),
),
],
),
),
]),
Expand Down
66 changes: 42 additions & 24 deletions third_party/packages/flutter_svg/lib/src/loaders.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,29 @@ abstract class SvgLoader<T> extends BytesLoader {
final SvgTheme theme = getTheme(context);
return prepareMessage(context).then((T? message) {
return compute((T? message) {
return vg
.encodeSvg(
xml: provideSvg(message),
theme: theme.toVgTheme(),
colorMapper: colorMapper == null
? null
: _DelegateVgColorMapper(colorMapper!),
debugName: 'Svg loader',
enableClippingOptimizer: false,
enableMaskingOptimizer: false,
enableOverdrawOptimizer: false,
)
.buffer
.asByteData();
try {
debugPrint('SvgLoader._load.provideSvg: empty');
final String xml = provideSvg(message);
if (xml.isEmpty) {
return Future<ByteData>.value(ByteData(0));
} else {
return vg
.encodeSvg(
xml: xml,
theme: theme.toVgTheme(),
colorMapper: colorMapper == null ? null : _DelegateVgColorMapper(colorMapper!),
debugName: 'Svg loader',
enableClippingOptimizer: false,
enableMaskingOptimizer: false,
enableOverdrawOptimizer: false,
)
.buffer
.asByteData();
}
} catch (e) {
debugPrint('SvgLoader._load.error: $e');
return Future<ByteData>.value(ByteData(0));
}
}, message, debugLabel: 'Load Bytes');
});
}
Expand Down Expand Up @@ -373,15 +382,19 @@ class SvgAssetLoader extends SvgLoader<ByteData> {
}

@override
Future<ByteData?> prepareMessage(BuildContext? context) {
return _resolveBundle(context).load(
packageName == null ? assetName : 'packages/$packageName/$assetName',
);
Future<ByteData?> prepareMessage(BuildContext? context) async {
try {
return await _resolveBundle(context).load(
packageName == null ? assetName : 'packages/$packageName/$assetName',
);
} catch (e) {
debugPrint('SvgAssetLoader.prepareMessage.error: $e');
return Future<ByteData?>.value();
}
}

@override
String provideSvg(ByteData? message) =>
utf8.decode(message!.buffer.asUint8List(), allowMalformed: true);
String provideSvg(ByteData? message) => utf8.decode(message!.buffer.asUint8List(), allowMalformed: true);

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

@override
Future<Uint8List?> prepareMessage(BuildContext? context) async {
final http.Client client = _httpClient ?? http.Client();
return (await client.get(Uri.parse(url), headers: headers)).bodyBytes;
try {
final http.Client client = _httpClient ?? http.Client();
final http.Response res = await client.get(Uri.parse(url), headers: headers);
return res.bodyBytes;
} catch (e) {
debugPrint('SvgNetworkLoader.prepareMessage.error: $e');
return null;
}
}

@override
String provideSvg(Uint8List? message) =>
utf8.decode(message!, allowMalformed: true);
String provideSvg(Uint8List? message) => message == null ? '' : utf8.decode(message, allowMalformed: true);

@override
int get hashCode => Object.hash(url, headers, theme, colorMapper);
Expand Down
18 changes: 18 additions & 0 deletions third_party/packages/flutter_svg/lib/svg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ 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.')
SvgTheme? theme,
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
});


/// Instantiates a widget that renders an SVG picture from an [AssetBundle].
///
/// The key will be derived from the `assetName`, `package`, and `bundle`
Expand Down Expand Up @@ -190,6 +192,7 @@ class SvgPicture extends StatelessWidget {
@Deprecated('Use colorFilter instead.')
ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
this.errorBuilder,
}) : bytesLoader = SvgAssetLoader(
assetName,
packageName: package,
Expand Down Expand Up @@ -251,6 +254,7 @@ class SvgPicture extends StatelessWidget {
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
SvgTheme? theme,
http.Client? httpClient,
this.errorBuilder,
}) : bytesLoader = SvgNetworkLoader(
url,
headers: headers,
Expand Down Expand Up @@ -308,6 +312,7 @@ class SvgPicture extends StatelessWidget {
this.clipBehavior = Clip.hardEdge,
SvgTheme? theme,
@Deprecated('This no longer does anything.') bool cacheColorFilter = false,
this.errorBuilder,
}) : bytesLoader = SvgFileLoader(file, theme: theme),
colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

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

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

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

/// The widget to show when failed to fetch, decode, and parse the SVG data.
final SvgPictureErrorWidgetBuilder? errorBuilder;

@override
Widget build(BuildContext context) {
return createCompatVectorGraphic(
Expand All @@ -505,6 +515,7 @@ class SvgPicture extends StatelessWidget {
placeholderBuilder: placeholderBuilder,
clipViewbox: !allowDrawingOutsideViewBox,
matchTextDirection: matchTextDirection,
errorBuilder: errorBuilder,
);
}

Expand Down Expand Up @@ -567,3 +578,10 @@ class SvgPicture extends StatelessWidget {
));
}
}

/// The signature that [VectorGraphic.errorBuilder] uses to report exceptions.
typedef SvgPictureErrorWidgetBuilder = Widget Function(
BuildContext context,
Object error,
StackTrace stackTrace,
);
Loading

0 comments on commit 4763502

Please sign in to comment.