Search code examples
androidfluttersvg

How to parse SVG or catch parsing errors with flutter_svg


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.

enter image description here

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!


Solution

  • 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,