lottie-flutter icon indicating copy to clipboard operation
lottie-flutter copied to clipboard

Precaching Lottie assets

Open davidgtu opened this issue 3 years ago • 12 comments

I have a lottie animation that I'm using as a loading screen:

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:lottie/lottie.dart';

class Loading extends StatelessWidget {
  final String loadingText;
  Loading({this.loadingText});

  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          if (loadingText != null) _buildLoadingText(loadingText),
          _buildAnimation(),
        ],
      ),
    );
  }

  Widget _buildLoadingText(String text) {
    return Text(
      loadingText,
      style: GoogleFonts.poppins(
          textStyle:
              TextStyle(fontWeight: FontWeight.w500, color: Colors.black)),
    );
  }

  Widget _buildAnimation() {
    return Lottie.asset('assets/lottie/heartbeat_loading.json',
        width: 300, repeat: true, animate: true);
  }
}

And I'm using it like this for when my app initially loads:

_determineHome() {
    return StreamBuilder(
      stream: AppBlocContainer().authenticationBloc().loggedIn,
      builder: (context, AsyncSnapshot<AuthenticationStatus> snapshot) {
        // return Loading();
        return AnimatedSwitcher(
            duration: Duration(seconds: 2),
            child: !snapshot.hasData
                ? Loading(
                    loadingText: 'Loading...',
                  )
                : _buildSecondChild(snapshot.data));
      },
    );

This works, except that the lottie animation is being loaded maybe a second or two too late, which by the time the lottie asset loads, it's too late and the page has already transitioned.

I was wondering since I did was able to precache my SVG images in my main() by doing this using flutter_svg:

    Future.wait([
      precachePicture(
        ExactAssetPicture(
            SvgPicture.svgStringDecoder, 'assets/svg/login.svg'),
        null,
      ),
      precachePicture(
        ExactAssetPicture(
            SvgPicture.svgStringDecoder, 'assets/svg/logo.svg'),
        null,
      ),
      precachePicture(
        ExactAssetPicture(
            SvgPicture.svgStringDecoder, 'assets/svg/signup_panel_1.svg'),
        null,
      )
    ]);

Would I be able to do the same thing with lottie?

davidgtu avatar Oct 03 '21 17:10 davidgtu

Hey @davidgtu, did you get to solve this?

ghost avatar Oct 13 '21 13:10 ghost

Would also like an answer here. 😬

mikenussbaumer avatar Nov 11 '21 18:11 mikenussbaumer

@SergioSanchezUltralytics Unfortunately no. Though it seems to be more of a device problem in where it runs slower. When viewing it on a more modern iOS device, it loads much quicker.

However, I still feel that this should be something that would prove helpful.

davidgtu avatar Nov 11 '21 18:11 davidgtu

Also having this issue

isAlmogK avatar Nov 21 '21 16:11 isAlmogK

@davidgtu Unsure if you still require info on this but when you do :


 Widget _buildAnimation() {
    return Lottie.asset('assets/lottie/heartbeat_loading.json',
        width: 300, repeat: true, animate: true);
  }

Within your Stateless widget , you will use resources & time to create your LottieBuilder (Lottie.asset returns a Builder) - this happens each time your widget requires building.

It would be better to create a LottieHelper cache singleton which basically caches at application startup (say) - the Lottie animation.

You would need something like this :

class LottieHelper {
  static LottieHelper _instance = LottieHelper._internal();

  final List<String> _lotties = [
    'assets/lottie/myLottieFile1.zip',
    'assets/lottie/myLottieFile2.zip',
    ];
  Dictionary<String, LottieBuilder> _lottieCache =
      Dictionary<String, LottieBuilder>();

  LottieHelper._internal() {
    /// Lets cache common Lottie Animations
    _lotties.forEach((asset) async {
      Uint8List ram;
      if (!_lottieCache.containsKey(asset)) {
        ram = Uint8List.view((await rootBundle.load(asset)).buffer);
        var lottie = Lottie.memory(ram);
      _lottieCache.add(KeyValuePair(asset, lottie));
    });
  }

  factory LottieHelper() => _instance;

You would then provide your (say) memory, file & asset methods which basically look inside the cache and returns a ready made LottieBuilder :

Widget asset(String asset, {double height, bool animate, Icon icon}) {
    /// Icon takes precedance when we dont have animations enabled
    if (icon != null && !_settingsManager.getShowAnimations()) {
      return icon;
    }
    if (height == null) {
      if (_lottieCache.containsKey(asset)) {
        return _lottieCache[asset];
      }else
      return Lottie.asset(asset,
          animate: animate ?? _settingsManager.getShowAnimations());
    } else {
      if (_lottieCache.containsKey(asset)) {
        return SizedBox(height: height, child: _lottieCache[asset]);
      } else {
        return Lottie.asset(asset,
            height: height,
            animate: animate ?? _settingsManager.getShowAnimations());
      }
    }
  }

This would prevent a LottieBuilder being generated each time and would simply return the already created cached copy (This would happen if say you are running on a Tablet and you ROTATE several times as an example).

Note also a second optimisation technique used here - we supply a compressed zip as opposed to a (potentially) very long JSON file which if very long would consume more memory than a zip file ....

alexda8999 avatar Feb 16 '22 17:02 alexda8999

We've been facing this issue even after implementing a cache for LottieBuilders as mentioned above. After some investigation, it appears that the delay is caused by the FutureBuilder returned by LottieBuilder. See https://github.com/xvrh/lottie-flutter/blob/master/lib/src/lottie_builder.dart#L457.

Caching LottieBuilder doesn’t help since it is still creating a new FutureBuilder each time. Our solution is to cache the LottieComposition returned by the FutureBuilder. This seems to fix the issue.

class LottieCache {

  final Map<String, LottieComposition> _compositions = {};

  /// Caches the given [LottieAsset]s.
  Future<void> add(String assetName) async {
      _compositions[assetName] = await AssetLottie(assetName).load();
  }
  
  Widget load(String assetName, Widget fallback) {
    final composition = _compositions[assetName];
    return composition != null ? Lottie(composition: composition) : fallback;
  }

}

EDIT: This is no longer required in Lottie ^2.0.0

Pante avatar Mar 15 '22 14:03 Pante

any updates on this problem?

ariefwijaya avatar Mar 22 '22 04:03 ariefwijaya

@Pante solution works like charm! Thanks

filofan1 avatar Apr 04 '22 11:04 filofan1

@Pante We experience some lag/screen-stutter upon first animation, but we are using 2.3.2.

Is your suggestion with the LottieCache still needed or not? And any ideas what can be done to prevent the lag upon the first/initial load of the animation? If the animation is re-played at a later stage, there is no lag.

simplenotezy avatar May 12 '23 14:05 simplenotezy

@simplenotezy We haven’t experienced any stuttering after updating to ^2.0.0.

The original issue was Lottie using a FutureBuilder internally which cause it to render later than the rest of the screen. I would probably start with checking if the Lottie animation is wrapped in a FutureBuilder/StreamBuilder.

Pante avatar May 13 '23 01:05 Pante

I had still had this but I minimized the JSON files which helped and removed any large animations I had

isAlmogK avatar May 13 '23 10:05 isAlmogK

@simplenotezy We haven’t experienced any stuttering after updating to ^2.0.0.

The original issue was Lottie using a FutureBuilder internally which cause it to render later than the rest of the screen. I would probably start with checking if the Lottie animation is wrapped in a FutureBuilder/StreamBuilder.

I call it pretty straightforward, no any special future/stream builders, just followed the documentation:

Lottie.asset(
  'assets/lottie/confetti.json',
  controller: confettiAnimationController,
  repeat: false,
  fit: BoxFit.cover,
)

The JSON file is already minimized and is around ~500kb

simplenotezy avatar May 15 '23 19:05 simplenotezy