Images in listview are not released from memory when out of view

Viewed 2581

I am displaying images from the internet in a vertical ListView. I fetch the images using http.get (not using cached network image cuz I do not want to cache the images). Then I insert the image Uint8List into image.memory(). What happens is that as the user scrolls the list and images are loading, the ram keeps increasing until the whole app crashes. Any ideas what to do ?

4 Answers

Yeah, this is the normal behavior. I don't know why exactly. My theory is that the images by default are disposed if the dart objects holding references to them are garbage collected rather then when the widgets are knocked off the widgets tree, but don't take my word for it- that's just my personal reasoning. It may be completely wrong.

To get around this, I use Extended Image pakcage It's constructors take a bool clearMemoryCacheWhenDispose which disposes of images in RAM in scroll lists. You may do that or visit the package code to see how he's doing it, and replicate it.

Other advice I can give is to make sure your images are of the appropriate size. If you are loading your images locally check this part of the documentation to have different dimensions selected for different screen sizes https://flutter.dev/docs/development/ui/assets-and-images#loading-images

If you are fetching your images from network which is more likely, make sure their sizes are appropriate, and have different sizes served if you can. If you don't have control over that set cacheWidth and cacheHeight in Image.memory these will reduce the cached image in memory (RAM). You see by default Flutter caches images in full resolution despite their apparent size in the device/app. For example if you want your image to display in 200x200 box then set cacheWidth to 200 * window.devicePixelRatio.ceil() you will find window in dart:ui, and if you only set cacheWidth, the ratio of your images will remain true. Same true if you only set cacheHeight. Also do use ListView.builder as suggested.

I am disappointed at how little is said about this in Flutter docs. Most of the time people discover this problem when their apps start crashing. Do check your dev tools regularly for memory consumption. It's the best indicator out there.

Cheers and good luck

I was having the same issue and found a fix thanks to @moneer!

Context:

Users in my app can create shared image galleries which can easily contain several hundred images. Those are all displayed in a SliverGrid widget. When users scrolled down the list, too many images were loaded into RAM and the app would crash.

Things I had already implemented:

  • Image resizing on the server side and getting the appropriate sized images on the client based on the device pixel ratio and the tile size in the gallery
  • I made sure that my image widgets were properly disposing when out of view, but the memory size kept building up as the user scrolled through all the images anyway
  • Implement cacheHeight to limit the size of the cached image to the exact size of the displayed image

All these things helped but the app would eventually still crash every time the user scrolled down far enough.

The fix:

After some googling I stumbled upon this thread and tried the extended_image_package as @moneer suggested. Setting the clearMemoryCacheWhenDispose flag to true fixed the issue of the app crashing as it was now properly clearing the images from memory when they were out of view. Hooray! However, in my app users can tap on an image and the app navigates to an image detail page with a nice Hero animation. When navigating back the image would rebuild and this would cause a rebuild 'flicker'. Not that nice to look at and kind of distracting.

I then noticed that there's also an enableMemoryCache flag. I tried setting this to false and that seems to work nicely for me. The Network tab in Dart DevTools seems to show that all images are only fetched from the network once when scrolling up and down the gallery multiple times. The app does not crash anymore.

I'll have to more testing to see if this leads to any performance issues (if you can think of any please let me know).

The final code:

ExtendedImage.network(
  _imageUrl,
  cacheHeight: _tileDimension,
  fit: BoxFit.cover,
  cache: true, // store in cache
  enableMemoryCache: false, // do not store in memory
  enableLoadState: false, // hide spinner
)

I had a similar issue when I loaded images from files in a ListView.

[left-side: old code, right-side: new code]

The first huge improvement for me: not to load the whole list at once

  • ListView(children:[...]) ---> ListView.builder(...).

The second improvement was that images are no longer loaded at full-size:

  • Image.file("/path") ---> Image.file("/path", cacheWidth: X, cacheHeight: Y)

These two things solved my memory problems completely

Ideally caching happens kind of by default after some conditions are fulfilled. So its upon your app to be responsible to handle and control how the caching will happen. Checkout this answer https://stackoverflow.com/a/24476099/12264865

Related