Flutter Caching Strategies: Memoization, ImageCache, Disk Caches, and DevTools
Layer caching in a Flutter app: in-memory memoization, ImageCache with cacheWidth, cached_network_image on disk, HTTP interplay, and measuring it all in DevTools.
Published on • November 10, 2026
AI Assistant

A Flutter app usually has several independent caches stacked on top of each other, and fixing “the app is slow” means knowing which layer is missing an entry. This post walks the stack from plain Dart memoization up through the framework ImageCache, the disk cache behind cached_network_image, and how HTTP headers do (and do not) interact with all of it — then shows how to measure the result in DevTools.
Layer 1: in-memory memoization
The cheapest cache is a map you own. Wrap expensive-but-pure work and give it an explicit eviction rule rather than letting it grow forever:
class MemoCache<V> {
MemoCache({this.maxEntries = 128});
final int maxEntries;
final _entries = <String, V>{};
V getOrCompute(String key, V Function() compute) {
final hit = _entries[key];
if (hit != null) {
// Refresh recency for a simple LRU policy.
_entries.remove(key);
_entries[key] = hit;
return hit;
}
final value = compute();
_entries[key] = value;
if (_entries.length > maxEntries) {
_entries.remove(_entries.keys.first);
}
return value;
}
void invalidate(String key) => _entries.remove(key);
void clear() => _entries.clear();
}
Keep this layer for parsed JSON, decoded config, computed layout values — anything whose size you can bound. It dies with the isolate, so it never has to be persisted or versioned.
Layer 2: ImageCache and cacheWidth/cacheHeight
Flutter keeps a shared least-recently-used ImageCache holding up to 1000 images and 100 MB, tunable through maximumSize and maximumSizeBytes. You rarely touch it directly; ImageProvider resolves into it via putIfAbsent, and you can inspect or clear it yourself:
final cache = PaintingBinding.instance.imageCache;
debugPrint('${cache.currentSize} entries, ${cache.currentSizeBytes} bytes, '
'${cache.liveImageCount} live');
// Drop one image after an upload, or everything on a hard reset.
await NetworkImage(url).evict();
cache.clear();
The memory trap is decode size, not download size: the docs note a 4K image costs over 30 MB once decoded. cacheWidth and cacheHeight tell the engine to decode at a smaller size instead:
Image.network(
avatarUrl,
cacheWidth: 256, // decode at 256 px wide, not the natural size
fit: BoxFit.cover,
)
Layout is unaffected — the widget still paints at its constraints — only the decoded buffer shrinks (a 3840x2160 image rendered at 384x216 drops from ~30 MB to ~330 KB). Two gotchas from the API docs: on web these parameters are ignored, since the browser delegates decoding, and an image larger than maximumSizeBytes is no longer admitted to the cache at all, so oversized images cause repeated refetches instead of a one-off bloat.
Layer 3: disk caching with cached_network_image
ImageCache is memory only and evaporates between launches. For bytes on disk use cached_network_image (current release 4.0.6), which stores and retrieves files through flutter_cache_manager (declared as flutter_cache_manager: ^3.4.5):
dependencies:
cached_network_image: ^4.0.6
CachedNetworkImage(
imageUrl: 'https://cdn.example.com/photo.jpg',
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.broken_image),
memCacheWidth: 400, // in-memory decode budget (ResizeImage)
maxWidthDiskCache: 800, // store a resized copy on disk
cacheKey: 'photo-v2', // your explicit version handle
)
The three size knobs are distinct layers: memCacheWidth/memCacheHeight resize in memory, maxWidthDiskCache/maxHeightDiskCache resize what is written to disk, and the raw file lives in the cache directory until the manager evicts it. One caveat from the readme: the web implementation has minimal support and does not include caching, so on web you fall back to the browser’s own cache.
HTTP caching interplay and invalidation
Flutter’s image layer does not honor response headers for its own cache: the Image.network docs state that all network images are cached regardless of HTTP headers. Cache-Control therefore cannot invalidate anything inside ImageCache, and the cache key is the provider identity (URL plus any ResizeImage parameters), not the response. In practice that means invalidation is your job, and there are three levers:
// 1. Version the key - simplest and most reliable.
Image.network('https://cdn.example.com/photo.jpg?v=$contentVersion');
// 2. Evict both layers after the user edits the resource.
await CachedNetworkImage.evictFromCache(url, cacheKey: 'photo-v2');
// 3. Nuke the disk cache entirely on logout.
await DefaultCacheManager().emptyCache();
Pick a strategy per data class: content-addressed URLs (hash in the path) need no invalidation at all, mutable resources need an explicit cacheKey bumped on write, and per-user data should be cleared on sign-out so it cannot leak across accounts.
Measuring with DevTools
Open the DevTools Memory view and work the three tabs the docs describe: Profile Memory shows current allocation by class (filter for Image and ImageStreamCompleter), Diff Snapshots compares a snapshot taken before an interaction with one taken after — use the Filter classes and packages button to isolate your cache classes — and Trace Instances records which methods allocate a selected class while you scroll the list in question. The chart’s Dart/Flutter Native series is where decoded images show up, since they are native objects outside the Dart heap. A cache that is working looks like memory climbing to a ceiling and flattening; a cache that is broken looks like a staircase that never levels off.
Takeaway: Bound every layer explicitly — a small LRU for computed values, cacheWidth/cacheHeight plus the 100 MB ImageCache budget for pixels, cached_network_image with memCacheWidth and maxWidthDiskCache for disk, and versioned keys for invalidation since HTTP headers never reach Flutter’s cache — then confirm the ceiling with DevTools memory snapshots.
Sources
- https://docs.flutter.dev/ui/assets/assets-and-images
- https://docs.flutter.dev/cookbook/images/network-image
- https://docs.flutter.dev/perf
- https://docs.flutter.dev/tools/devtools/memory
- https://docs.flutter.dev/release/breaking-changes/imagecache-large-images
- https://api.flutter.dev/flutter/painting/ImageCache-class.html
- https://api.flutter.dev/flutter/widgets/Image-class.html
- https://pub.dev/packages/cached_network_image