cachet_image
A modern, isolate-friendly cached network image package for Flutter — built with Dart 3 sealed classes, stream-based load states, and a real LRU disk cache.
Why not cached_network_image?
cachet_image targets the same problem with a more current Dart 3
approach:
- Sealed state, not booleans.
CachetStateis a sealed class (CachetLoading/CachetLoaded/CachetError) so UI code uses exhaustiveswitchexpressions instead of juggling nullable flags. - Structured failures.
CachetFailuresplits intoCachetNetworkFailure,CachetDecodeFailure, andCachetCacheFailureso you can branch on why something failed. - Stream-based progress.
CachetLoader.load()returns aStream<CachetState>, so download progress, cache hits, and errors are all just events on one stream — easy to wire into Riverpod/Bloc. - Real LRU eviction. The cache manager tracks size + last-access
time per entry and evicts oldest-first once you exceed
maxCacheBytes, instead of relying on the OS to clean up a temp folder. - Background decoding.
decodeImageOffMainIsolateis exposed separately so you can decode large images without blocking the UI thread, independent of the fetch/cache pipeline.
Install
dependencies:
cachet_image: ^0.1.0
Usage
import 'package:cachet_image/cachet_image.dart';
CachetImage(
url: 'https://example.com/photo.jpg',
fit: BoxFit.cover,
placeholderBuilder: (context, progress) => Center(
child: CircularProgressIndicator(value: progress),
),
errorBuilder: (context, failure) => switch (failure) {
CachetNetworkFailure() => const Icon(Icons.wifi_off),
CachetDecodeFailure() => const Icon(Icons.broken_image),
CachetCacheFailure() => const Icon(Icons.storage),
},
)
Configuring the cache
Call this once at startup, before any CachetImage widgets build,
if you want non-default limits:
void main() {
CachetCacheManager.configure(
maxCacheBytes: 100 * 1024 * 1024, // 100 MB
maxAge: const Duration(days: 14),
);
runApp(const MyApp());
}
Clearing the cache
await CachetCacheManager.instance.clearAll();
await CachetCacheManager.instance.evictUrl('https://example.com/photo.jpg');
final stats = await CachetCacheManager.instance.stats();
print('${stats.entryCount} entries, ${stats.totalBytes} bytes');
Using the loader directly
If you want the raw bytes/stream without the widget (e.g. to
pre-warm the cache, or feed a custom ImageProvider):
final loader = CachetLoader();
await for (final state in loader.load('https://example.com/photo.jpg')) {
switch (state) {
case CachetLoading(:final progress):
print('loading: $progress');
case CachetLoaded(:final bytes, :final fromCache):
print('loaded ${bytes.length} bytes (fromCache: $fromCache)');
case CachetError(:final failure):
print('error: ${failure.message}');
}
}
Project layout
lib/
cachet_image.dart # public API barrel
src/
models.dart # CachetState / CachetFailure sealed classes
cache_manager.dart # disk cache + LRU eviction
loader.dart # fetch + cache-check stream
cachet_image_widget.dart # the CachetImage widget
example/
main.dart # grid demo
Roadmap ideas
- AVIF/WebP-aware format negotiation with fallback
- BlurHash / ThumbHash placeholder decoding built in
- Priority-queue prefetching for list scrolling
- Pluggable cache backend (e.g. sqlite3 for large caches with querying)
License
MIT
Libraries
- cachet_image
- A modern, isolate-friendly cached network image package for Flutter.