mikeesouth
mikeesouth

Reputation: 1640

Preload (all) image assets in a Flutter app

I would like an easy approach to preload/cache all of my static image assets so that they can be rendered/served without a delay.

I've seen that there is a precacheImage() call that can be used to pre-load/cache the AssetImage. This needs a context and it is recommended to call this in the didChangeDependencies() override.

Shouldn't there be a way to make this easier and more general? My app uses a total of 1.5 MB of image data (and I've included 2.0x and 3.0x upscaled versions in that number). PNG images that are 50 KB (and no upscaled versions) takes a noticeable amount of time to display, maybe 300-600ms on both emulator and fast devices. These are local assets, not fetched over the network. I find that irritating and I'm frustrated that there isn't a better way to handle this?

I've also seen the tip to use FadeInImage but again - it's not really what I'm looking for.

I'm displaying the image in a stateless widget (a custom button). It's not possible to use precacheImage in a stateless widget afaik. So I'd need to build the Image.asset() in my parent widget, call precacheImage and then pass the image widget to my stateless widget and render it in build - this is cumbersome.

Furthermore, the images will be displayed in different places (different parent widgets). Sometimes the image widgets differ in size between widgets and since size is parameters to Image.asset() I guess I would need to precache each unique size and pass these precached image widgets around. Isn't it possible to tell Flutter to "cache" the data of the PNG so that when the Image.asset is requested it reads the PNG from cache - without having to pass around precached image widgets?

I would like a "precacheAllImageAssets()call or callprecacheImage()` with a string so that each Image.asset() that references that same asset would be cached.

I guess that Flutter internally caches the image widget (including it's size and other properties) as some internal render object that is cached. Thus pre-caching two different sizes of the same image would require two different caches. With that being said - I'd still want a precacheAllImageAssets() call that could at least read the PNG data into memory and just serve it quicker even if would need to do some processing to get the PNG data to an actual widget with a size before it could be rendered. With such a cache I could maybe get a render delay of < 50 ms instead of the current 300-600 ms.

Any idea if this is possible? If not possible - am I missing something obvious or could this be a (likely) future improvement of the Flutter framework?

Upvotes: 5

Views: 7762

Answers (2)

Georgii
Georgii

Reputation: 472

UPDATE: after some research I've figured out that previous version of my answer was not correct. Here is relevant one (you can see old one in edit history).

You do not need to use same ImageProvider for images to precache. So you can run precacheImage() at init time and then create another image with same path and it is gonna be obtained from cache (if it was not cleared one way on another).

Internally precacheImage() uses ImageProvider.obtainKey() which is pair of (imagePath, scale) as a key to store image in in-memory cache. So as long as you are using same key it does not matter which instance of ImageProvider/Image you are using.

For futher insights you can inspect ImageCache documentation. Specifically, take a look at putIfAbsent() method as it's main caching endpoint. To understand how images generate their key (at which they are stored in ImageCache) try to start with ImageProvider class and then look into it's implementations.

Upvotes: 2

liping
liping

Reputation: 79

Here is my similar precacheAllImageAssets(), but you need to list all image path by yourself.

final   List _allAsset = [
  ///tabbar
      'images/tabbar/tabar_personal.png',
      'images/tabbar/tabar_personal_slt.png',
      'images/tabbar/tabar_home.png',
      'images/tabbar/tabar_home_slt.png',
      'images/...'
      'images/...'
}

void main() {
  final binding = WidgetsFlutterBinding.ensureInitialized();

  binding.addPostFrameCallback((_) async {
    BuildContext context = binding.renderViewElement;
    if(context != null)
      {
        for(var asset in _allAsset)
        {
          precacheImage(AssetImage(asset), context);
        }
      }
  });
}

Upvotes: 5

Related Questions