Starting in version 2.0, flutter_svg
migrated to using the
vector_graphics_compiler
as an SVG parsing backend and the vector_graphics
runtime package for widget/render object responsibilities.
The vector_graphics
packages provide a compiler runtime that has no
dependencies on dart:ui
, and so can run completely as a CLI application or in
a background isolate. It produces a binary file that has no versioning or
compatibility guarantees - meaning that the output of the compiler is only
suitable for use with the runtime and codec with the same exact git hash. This
allows for a compact, optimized, and further optimizable format.
The ideal way to use that package is to convert your assets at compile time and
bundle them with your application, or store them on a server in a location that
is matched precisely to git hash of the package that produced them. This can
be challenging with the current state of the flutter_tools and Dart build
systems, as well as for developers who lack the resources to version and control
all of their network vector graphic assets. This package is meant to help bridge
the gap, as well as to help existing flutter_svg
users benefit from more
optimizations and an easier migration path.
SVG, as specified, is a nightmare. Parsing XML is slow, especially in comparison
to binary formats like Flat/Protocol Buffers and the like. SVG itself pulls in
many other web standards like CSS, JavaScript, and HTML. Even if you restrict
yourself to some static subset of SVG, the specification allows for a wide
latitude of insane but valid things. For example, the specification does nothing
to prevent or discourage an editor from inserting dozens of <g>
nodes, each
having a transform
, that eventually transform their sole child back to the
identity matrix. And because the specification states that each group node can
have other inheritable attributes attached, each node must be examined and some
suitable data structure(s) must be allocated to account for how to pass on
those heritable attributes if any arise. On a slow, lost-cost mobile device,
this cuts into valuable frame-time if done on the UI isolate. And this doesn't
even mention that design tool may have snuck in any number of masks, which as
specified require at least two render target switches but frequently are used in
place of what could be achieved with a much cheaper clip.
The basic problem is that SVG gives design tools a very large number of ways to specify a drawing, and design tools are generally not written to produce SVG that will be fast to render or fast to parse. Existing SVG optimization tools tend to focus on network download size, and may lack internal capabilities to examine the relationships between nodes to decide when and how they can be optimized.
The vector_graphics
suite addresses this by focusing less on being fast at
parsing and more on being able to produce something that will be fast when it
finally gets to the UI isolate - and faster when Flutter's rasterizer goes to
render it. Some of the optimizations available that suite are currently only
available when directly using the vector_graphics
package, both because of
challenges around packaging FFI libraries for various platforms and because
some of the optimization algorithms may be more expensive than just taking the
penalty of not using them.
Additionally, the design that flutter_svg
was using took inspiration from
package:flutter
's image related classes. This turned out to be a mistake.
There is no intention for flutter_svg
to ever support animated/multi-frame
SVGs, and the way that Flutter ties together byte acquisition and image decoding
makes things a bit complicated. Instead, vector_graphics
introduces the
concept of a BytesLoader
class, which knows how to obtain encoded bytes to
feed to the runtime via an asynchronous method that optionally receives a
BuildContext
. See the loaders.dart
file in this package for examples.
As of this writing, the optimizations that are available include:
- Useless element/attribute pruning.
- Inheritance removal.
- Paint/path deduplication.
- Transform pre-calculation/collapsing.
- Elimination of XML parsing on the UI isolate.
- Elimination of UTF-8 and Base 64 decoding (i.e. for embedded images) on the UI isolate.
- Path dashing is done completely on a background isolate.
- More use of 32-bit floats instead of 64-bit doubles, which saves on memory with good visual fidelity.
Compared to prior releases of flutter_svg, the following changes can be expected:
- Loss of support for
text
elements that usedx
ordy
attributes, which already was implemented only partially and incorrectly. - Support for out of order
defs
and element references. - Support for patterns.
- Gradients render slightly differently. This is likely due to a combination of some precision differences and pre-calculating transforms.
- Some shapes, such as circles and rounded rects, render slightly differently. This is due to precision differences and pre-calculation of curves.
- Golden tests may now show SVGs they did not used to show. this is because more parsing and rendering is completely synchronous in tests, whereas previously it was always at least partially async.
- Outside of tests (where the extra async makes life more confusing) and web (which does not have isolates), parsing happens in a separate isolates.
This API doesn't exist anymore. If you were using it to pre-fetch bytes from
some PictureProvider
, instead write a custom BytesLoader
implementation
(or use an existing one) and use vg.loadPicture
. However, there is currently
no provided widget to make drawing that picture easy, but generally speaking
you can use a CustomPainter
.
If you had a custom PictureProvider
, you will now want a custom BytesLoader
.
Consider overriding SvgLoader<T>
, which takes care of doing the right thing
with isolates for you and only requires you to implement a function to obtain
the SVG string that will be run in a non-UI isolate. See more examples in
loaders.dart
in this package.
Pictures are no longer cached, however the byte data for vector_graphics output
is cached in the Cache
object. This is similar to but not quite the same as
the old picture cache. It is available via svg.cache
.
The SvgPicture.clipBehavior
property is deprecated (no-op).
The SvgPicture.cacheColorFilter
property is deprecated (no-op).
The SvgPicture.*
constructors still have color
and colorBlendMode
properties, which are deprecated. Instead use the colorFilter
property. To
achieve the old behavior of the color
property, use
ColorFilter.mode(color, BlendMode.srcIn)
.
The SvgPicture.pictureProvider
property has been removed. Use the loader
property instead, if you must.
Please limit your golden tests. In particular, try to make sure that you avoid testing the same SVG over and over again in multiple different golden tests, and try to make sure that you only have as many golden files that as can be reviewed by you and your team if they all changed in a single update. In general, this is somewhere around 10-20 golden files per reviwer, and that is generously assuming that each reviewer will carefully examine the differences in those 10-20 files. Assume that the reviewer will have no idea what the author of the test intended, even if the reviewer authored the test. If your team is 5 people, that means somewhere between 50-100 goldens.
Anecdotally, I have missed important regressions in golden tests I wrote because
I wrote them several years ago and just forgot, and I was only looking for
change X which I verified but missed changes Y and Z that I hadn't thought about
in over a year. I have also worked with partner projects that use flutter_svg
that have tens of thousands of golden images directly or indirectly involveing
SVGs and it is a nightmare for everyone when some minor change comes along
that touches most of them. Very frequently in such projects, it becomes obvious
that no one has ever looked at a large number of these golden tests, and they
actually harm test coverage because they falsely make broken code appear to have
coverage via a test that requires a human to double check its result.
With that said, most golden testing should "just work," except if your SVGs have images in them. In that case, "real" async work needs to get done, and at some point the test in question will have to call
await tester.runAsync(() => vg.waitForPendingDecodes());
await tester.pump();
to make sure that the image decode(s) finish and the placeholder widget is replaced with the actual picture.