I would like to parse SVG string to test if it is a valid SVG. Is this possible?
I am struggling a little with error handling. Try/catch does not work and neither does RunZonedGuarded. To see what I mean try to load an invalid svg string:
try {
SvgPicture.string('not svg');
}
catch {
//not catching
}
Below approach sort of catches the error without crashing the app, but not very gracefully. The unpleasant looking android error is displayed in the image box and it overflows.
FutureBuilder<String>(
future: Future.value(_shopLogoSvg),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return SvgPicture.string(snapshot.data!,
fit: BoxFit.contain,
allowDrawingOutsideViewBox: false,);
} else {
return const Center(
child: CircularProgressIndicator()); // or any other loading indicator
}
},
);
It would help to have SvgPicture.parse
method that could be used to parse non-trusted SVG strings before loading.
Or perhaps there is a better way to properly handle errors with flutter_svg
?
Any help appreciated!
the key is to use SvgStringLoader
and try to chatch errors while calling loadBytes()
method, something like this:
class FooSVG extends StatefulWidget {
const FooSVG({
super.key,
required this.svg,
this.onError,
});
final String svg;
final Widget Function(dynamic error)? onError;
@override
State<FooSVG> createState() => _FooSVGState();
}
class _FooSVGState extends State<FooSVG> {
Future<SvgStringLoader>? _loaded;
@override
void didChangeDependencies() {
debugPrint('FooSVG.didChangeDependencies');
super.didChangeDependencies();
_loaded = _load();
}
@override
void didUpdateWidget(FooSVG oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.svg != widget.svg) {
debugPrint('FooSVG.didUpdateWidget reload');
_loaded = _load();
} else {
debugPrint('FooSVG.didUpdateWidget no change');
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<SvgStringLoader>(
future: _loaded,
builder: (ctx, snapshot) {
// debugPrint(snapshot.toString());
return switch (snapshot) {
(AsyncSnapshot<SvgStringLoader> s) when s.hasData => SvgPicture(s.data!),
(AsyncSnapshot s) when !s.hasError => const UnconstrainedBox(),
(AsyncSnapshot s) => _error(s),
};
}
);
}
Future<SvgStringLoader> _load() async {
try {
final loader = SvgStringLoader(widget.svg);
await loader.loadBytes(context);
return loader;
} catch (e) {
debugPrint('FooSVG error: $e');
rethrow;
}
}
Widget _error(AsyncSnapshot snapshot) {
return widget.onError?.call(snapshot.error!) ?? Text(snapshot.error!.toString());
}
}
now call it with:
FooSVG(
svg: '''
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<g>
<g>
<path d="M4,4h7.59086V6.86658H7.43044V33.13342h4.16042V36H4Z" fill="#727474"/>
<path d="M28.40914,33.13342h4.159V6.86658h-4.159V4H36V36H28.40914Z" fill="#727474"/>
</g>
<path d="M17.61105,13.53573H11V9.01825H29v4.51748H22.39077V32.88H17.61105Z"/>
</g>
</svg>''',
onError: (error) {
return switch (error) {
XmlParserException(:final message, :final line, :final column) =>
Text('parser error, $message at [$line:$column]'),
_ => Text('other error, $error'),
};
},
),
if you want to check the error checking replace for example <svg
with <svg,