Contact Me
Blog

Embedded SVGs in Flutter

It's a free, MIT-licenced icon from IconFinder; you can download it from there.

In practice, you probably wouldn't render an icon in this way; that's what icon fonts are designed for. It's just a small SVG that serves as an example of this approach.

The SVG version looks like this:

<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg">
    <path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z"/>
    <path d="M0 0h48v48H0z" fill="none"/>
</svg>

The Jovial SVG package allows you to use SVG images in your Flutter app, typically from SVG files in your assets folders, or a more efficient binary format.

It also allows you to render an SVG from its underlying string representation; that's what we're going to do here.

flutter pub add jovial_svg

You'll need to import it:

import 'package:jovial_svg/jovial_svg.dart';

In order to display an SVG, you need to create a ScalableImageWidget. This takes an instance of the ScalableImage class. That class has a number of named constructors; among them, fromSvgString().

Let's look at how to do that.

class HomeIcon extends StatelessWidget {
  const HomeIcon({super.key});

  static const svgStr = '''<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg">
      <path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z"/>
      <path d="M0 0h48v48H0z" fill="none"/>
    </svg>''';

  @override
  Widget build(BuildContext context) {
    return ScalableImageWidget(
        si: ScalableImage.fromSvgString(svgStr),
    );
  }
}

Here we're creating a constant variable to hold the SVG code, creating an instance of ScalableImage using its fromSvgString() named constructor, and creating a ScalableImageWidget for it.

Let's improve this. For starters, the whole point of a scalable image is, well, that it can scale; being a vector, it can scale up without any degradation in quality.

ScalableImageWidget takes a scale property, which is a double; a value of 3 means it should be doubled in size, 0.5 means it should be halved.

That's only one way we can modify the image's size; the other is to place it in a SizedBox and instruct it to fill the space:

SizedBox(
    width: size,
    child: ScalableImageWidget(
        si: ScalableImage.fromSvgString(svgStr),
        fit: BoxFit.cover,
    )
)

Before we use that in the code, let's add another enhancement. One of the benefits of embedding an SVG image using a string, is that we can modify it; for example, dynamically changing the color or showing/hiding parts of an image. It also supports inline stylesheets to do similar.

Let's add a color property, then use that to dynamically set the color of the image.

We'll use an instance of Color, but we'll need a hexadecimal representation. Here's an easy way to do that:

color.value.toRadixString(16)

Let's put all of that together to create a new icon widget:

class HomeIconCustom extends StatelessWidget {
  const HomeIconCustom({
    super.key,
    Color? color,
    double? size,
  }): color = color ?? Colors.black, size = size ?? 48;

  final Color color;
  final double size;

  @override
  Widget build(BuildContext context) {

    String svgStr =  """<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg">
      <path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z" fill="#${color.value.toRadixString(16)}" />
      <path d="M0 0h48v48H0z" fill="none"/>
    </svg>""";

    return SizedBox(
      width: size,
      child: ScalableImageWidget(
        si: ScalableImage.fromSvgString(svgStr),
        fit: BoxFit.cover,
      )
    );
  }
}

Here's all of the code above as a fully-fledged Flutter app:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              HomeIcon(),
              HomeIconCustom(),
              HomeIconCustom(color: Colors.red,),
            ],
          )
        ),
      ),
    );
  }
}

class HomeIcon extends StatelessWidget {
  const HomeIcon({super.key});

  static const svgStr = '''<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg">
      <path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z"/>
      <path d="M0 0h48v48H0z" fill="none"/>
    </svg>''';

  @override
  Widget build(BuildContext context) {
    return ScalableImageWidget(
        si: ScalableImage.fromSvgString(svgStr),
    );
  }
}

class HomeIconCustom extends StatelessWidget {
  const HomeIconCustom({
    super.key,
    Color? color,
    double? size,
  }): color = color ?? Colors.black, size = size ?? 48;

  final Color color;
  final double size;

  @override
  Widget build(BuildContext context) {

    String svgStr =  """<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg">
      <path d="M20 40V28h8v12h10V24h6L24 6 4 24h6v16z" fill="#${color.value.toRadixString(16)}" />
      <path d="M0 0h48v48H0z" fill="none"/>
    </svg>""";

    return SizedBox(
      width: size,
      child: ScalableImageWidget(
        si: ScalableImage.fromSvgString(svgStr),
        fit: BoxFit.cover,
      )
    );
  }
}