dnfield / flutter_svg

SVG parsing, rendering, and widget library for Flutter
MIT License
1.66k stars 455 forks source link

Provide easy migration example(s) for `precachePicture` #841

Open Dohmanlechx opened 1 year ago

Dohmanlechx commented 1 year ago

I just updated this package to 2.0.0+1 and noticed how precachePicture no longer existed in the API.

Removed the affected functions from the "root page" of the app and yes, now my bottom navigation bar icons and the main logo are delayed. Didn't have this problem when I could precache the images.

I have found this: https://github.com/dnfield/flutter_svg/blob/master/vector_graphics.md#precachepicture but unfortunately, it didn't tell me anything. Please provide proper code examples of how to precache an SVG resource, would be highly appreciated.

hygehyge commented 1 year ago

@Dohmanlechx Maybe this would work.

const loader = SvgAssetLoader('res/large_icon.svg');
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
dnfield commented 1 year ago
  1. Are your icons still delayed in release/profile mode? See #837 - in debug mode things are slower right now.
  2. I can add something about precaching. The problem ends up being that people precache at "bad" times like startup, which delays whole app startup. It shouldn't be quite as bad with the new version because of isolates, but it's still not great.

The code that @hygehyge provided should work as well.

Dohmanlechx commented 1 year ago
  1. No, it's much better in profile mode! 🎉 The small icons in the bottom navigation bar load instantly, but the large SVG illustration we have on the main page still is a bit delayed. I wrote a similar comment here: https://github.com/dnfield/flutter_svg/issues/840#issuecomment-1416062838
  2. Actually, we prefer like 500 ms slower app startup than letting our end users see a jumpy layout when the big illustration eventually pops up. That isn't good UX. It would be so nice if you added them back, but I fully understand your concern and motive. I will try to convince our designer to sacrifice this big illustration to get a slightly faster app startup.

@Dohmanlechx Maybe this would work.

const loader = SvgAssetLoader('res/large_icon.svg');
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));

Worked like a charm! I really can't feel any difference in the app startup either. Thank you! @dnfield Feel free to close this issue anytime, but I believe this code example would help a lot of people.

levi956 commented 1 year ago
  1. Are your icons still delayed in release/profile mode? See I am seeing a significant performance regression on 2.0.0 #837 - in debug mode things are slower right now.
  2. I can add something about precaching. The problem ends up being that people precache at "bad" times like startup, which delays the whole app startup. It shouldn't be quite as bad with the new version because of the isolates, but it's still not great.

The code that @hygehyge provided should work as well.

The color parameter seems to be deprecated, Please what's the replacement for color SVGs?

levi956 commented 1 year ago

@Dohmanlechx Maybe this would work.

const loader = SvgAssetLoader('res/large_icon.svg');
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));

This seems like a single asset file, is it possible to add an asset path for the whole svg path and place it in void main startup?

swiftymf commented 1 year ago

The color parameter seems to be deprecated, Please what's the replacement for color SVGs?

I switched to using colorFilter by passing in my previous colors into a ColorFilter like this: colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),

Looks like that is basically the default when you don't pass into anything for colorFilter and it's working for me.

swiftymf commented 1 year ago
  1. Are your icons still delayed in release/profile mode? See I am seeing a significant performance regression on 2.0.0 #837 - in debug mode things are slower right now.

This was exactly my case. I was running debug builds and it was slow. Once I found this I tried a release build and it's looking good. 👍

simplenotezy commented 1 year ago

@hygehyge is there a reason you don't include the context, and instead pass null?

hygehyge commented 1 year ago

@levi956 You can iterate asset names like this.

final manifestJson = await rootBundle.loadString('AssetManifest.json')
final images = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/images'));

@simplenotezy I use that code to load a large icon for startup screen, on entry point(void main()). So there is no BuildContext yet.

sikandernoori commented 1 year ago

Based on @hygehyge example I have implemented below in main before running app.

      final manifestJson = await rootBundle.loadString('AssetManifest.json');
      List svgsPaths = (json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/images/') && key.endsWith('.svg')) as Iterable).toList();

      for(var svgPath in svgsPaths as List<String>) {
        var loader = SvgAssetLoader(svgPath);
        await svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
      }

and this is how I am getting svg instance.

  static SvgPicture get getSomeImage => SvgPicture.asset(
        "assets/images/some_image.svg",
        height: 40,
        width: 40,
        color: Colors.white,
      );
olegyablokov commented 1 year ago

What if I use ".svg.vec" files as described in "Precompiling and Optimizing SVGs" section in README in flutter_svg? Should I cache these files and if yes then how?

dnfield commented 1 year ago

You should not need to cache the .vec files.

keithhie commented 1 year ago
      final manifestJson = await rootBundle.loadString('AssetManifest.json');
      List svgsPaths = (json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/images/') && key.endsWith('.svg')) as Iterable).toList();

      for(var svgPath in svgsPaths as List<String>) {
        var loader = SvgAssetLoader(svgPath);
        await svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
      }

This code snippet provided by @sikandernoori used to work for me, but all of a sudden, I'm facing this issue below:

Unhandled Exception: 'package:vector_graphics_codec/vector_graphics_codec.dart': Failed assertion: line 771 pos 12: 'format == 0': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
#2      VectorGraphicsCodec.writeImage (package:vector_graphics_codec/vector_graphics_codec.dart:771:12)
#3      _encodeInstructions (package:vector_graphics_compiler/vector_graphics_compiler.dart:171:11)
#4      encodeSvg (package:vector_graphics_compiler/vector_graphics_compiler.dart:142:10)
#5      SvgLoader._load.<anonymous closure>.<anonymous closure> (package:flutter_svg/src/loaders.dart:137:14)
#6      _testCompute (package:flutter_svg/src/utilities/compute.dart:12:38)
#7      SvgLoader._load.<anonymous closure> (package:flutter_svg/src/loaders.dart:135:21)
felipecastrosales commented 1 year ago

Hi, any expectation about this?

feinstein commented 1 year ago

FYI: I see my bottom navigator SVGs "blinking" when the main screen opens, the same behavior we see with asset images. My bet is not on this library's performance, but on the time that takes from assets to be retrieved from storage.

I am on Flutter 3.10.7 and I can't see this on iOS, only on Android.

felipecastrosales commented 1 year ago

@feinstein Yesterday I also noticed this in my app, precisely in my bottom navigation - like you. In fact, in a part of my app I decided to migrate the assets to png and perform a precacheImage and the difference in performance was absurd - much better.

feinstein commented 1 year ago

There are other ways of solving this:

1 - Precache SVGs as described here.

2 - Create a dart file where each of your SVGs are a String. Since they will be part of your code, they don't need to be loaded from storage.

3 - Transform your SVGs into a font and use them as Icons (that's they way Flutter Icons work).

DanMossa commented 9 months ago

You should not need to cache the .vec files.

@dnfield I'm using the compiled .vec images and still experience a delay before the image renders

SvgPicture(AssetBytesLoader("assets/logo_lights_large.svg.vec"), height: 50),
eshfield commented 9 months ago

Based on @hygehyge example I have implemented below in main before running app

I have found a way to do the same without AssetManifest.json file:

Future<void> precacheSvgImages() async {
  final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle);
  final assets = assetManifest.listAssets();

  // the same code below
  final svgPaths = assets.where((path) => path.endsWith('.svg'));
  for (final svgPath in svgPaths) {
    final loader = SvgAssetLoader(svgPath);
    await svg.cache
        .putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
  }
}

This function has to be called inside of initState() method of MainApp for example—it will not work in the main() before runApp started.

cedvdb commented 7 months ago

There are other ways of solving this:

1 - Precache SVGs as described here.

2 - Create a dart file where each of your SVGs are a String. Since they will be part of your code, they don't need to be loaded from storage.

3 - Transform your SVGs into a font and use them as Icons (that's they way Flutter Icons work).

For 3. I don't think fonts can have multiple colors can they ?

For 2. That behavior is different for the web for multiple reasons

feinstein commented 7 months ago

Never tested it on the Web, could you elaborate more on why it doesn't work in the web?

cedvdb commented 7 months ago

assets are dwnloaded separately. Http2 parallelism and browser caching are some of the things you give up on the web by putting the svg inside your dart code.

feinstein commented 7 months ago

Good point, but unless you have lots of SVGs, or huge ones, this shouldn't be a problem in today's internet speeds, since SVGs are tiny.