flutter_media_cache 3.0.0 copy "flutter_media_cache: ^3.0.0" to clipboard
flutter_media_cache: ^3.0.0 copied to clipboard

A powerful Flutter package for caching images and videos with customizable expiration time and automatic cache management.

flutter_media_cache #

Platform License pub.dev License: MIT style: flutter_lints

Production-grade media caching for Flutter — images and videos — with a priority download queue, automatic LRU eviction, deduplication, exponential- backoff retry, per-download progress streams, and conditional HTTP GET.

Supports all Flutter platforms: Android · iOS · Web · macOS · Windows · Linux.


Features #

Feature Details
🗂️ Two-tier cache In-memory LRU + disk storage
🚦 Priority queue high / normal / low per request
🔁 Deduplication Same URL → one in-flight download
🔄 Retry Exponential backoff, configurable attempts
📡 Progress stream Stream<DownloadProgress> with byte counts
🌐 Conditional GET ETag / If-None-Match / Last-Modified
🧹 LRU eviction Separate limits for memory (items) and disk (bytes)
🎬 Video support CachedVideo widget with built-in controls
⏸️ Cancellation Cancel any queued or in-flight download
📦 Preloading preloadAll([url1, url2, ...])
📊 Analytics Hit rate, miss count, total cache size
🌍 All platforms Android · iOS · Web · macOS · Windows · Linux

Getting started #

dependencies:
  flutter_media_cache: ^3.0.0

Initialize once #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await MediaCacheManager.initialize(
    config: CacheConfig(
      maxDiskBytes: 300 * 1024 * 1024,  // 300 MB disk
      maxMemoryItems: 200,               // 200 items in RAM
      maxAge: const Duration(days: 30),
      maxConcurrentDownloads: 6,
      maxRetries: 3,
    ),
  );

  runApp(const MyApp());
}

Alternatively, use CacheManagerProvider near the root of your widget tree and skip the manual initialize call:

return CacheManagerProvider(
  config: CacheConfig(maxDiskBytes: 300 * 1024 * 1024),
  child: MaterialApp(home: HomePage()),
);

Usage #

Display a cached image #

CachedImage(
  imageUrl: 'https://picsum.photos/400/300',
  width: 400,
  height: 300,
  fit: BoxFit.cover,
  borderRadius: BorderRadius.circular(12),
  priority: DownloadPriority.high,
  placeholder: const ShimmerBox(),     // optional
  errorWidget: const ErrorPlaceholder(), // optional
)

Custom progress indicator #

CachedImage(
  imageUrl: 'https://example.com/large.jpg',
  progressIndicatorBuilder: (context, url, progress) {
    return CircularProgressIndicator(value: progress.progress);
  },
)

Display a cached video #

CachedVideo uses a builder pattern — you provide any video player widget via the builder callback. The CacheResult exposes url, filePath, bytes, and isFromCache.

CachedVideo(
  videoUrl: 'https://example.com/clip.mp4',
  builder: (context, result) => AspectRatio(
    aspectRatio: 16 / 9,
    child: YourVideoPlayer(filePath: result.filePath),
  ),
)

With a custom loading state:

CachedVideo(
  videoUrl: 'https://example.com/clip.mp4',
  placeholder: const MyLoadingWidget(),
  builder: (context, result) => VideoPlayerWidget(path: result.filePath),
)

Programmatic fetch (bytes) #

final result = await MediaCacheManager.instance.getMedia(
  'https://example.com/image.jpg',
  priority: DownloadPriority.high,
);

if (result.bytes != null) {
  // Use result.bytes (Uint8List) or result.filePath (String? on non-web)
}

Fetch with progress stream #

final (:result, :progress) = MediaCacheManager.instance.getMediaWithProgress(
  'https://example.com/video.mp4',
);

progress.listen((p) {
  print('${p.bytesDownloaded} / ${p.totalBytes ?? "?"}  '
        '— ${p.status.name}');
});

final data = await result;

Custom progress UI with DownloadProgressBuilder #

DownloadProgressBuilder(
  url: 'https://example.com/video.mp4',
  builder: (context, progress, child) {
    if (progress == null || progress.isCompleted) return child!;
    return LinearProgressIndicator(value: progress.progress);
  },
  child: const Icon(Icons.check_circle, color: Colors.green),
)

Preloading #

// Fire-and-forget; uses low priority so it doesn't block visible content.
await MediaCacheManager.instance.preloadAll([
  'https://example.com/next1.jpg',
  'https://example.com/next2.jpg',
]);

Cache management #

final manager = MediaCacheManager.instance;

// Is this URL cached?
final cached = manager.isCached('https://example.com/image.jpg');

// Remove one entry.
await manager.removeEntry('https://example.com/image.jpg');

// Remove all expired entries.
await manager.clearExpired();

// Wipe everything.
await manager.clearAll();

// Analytics snapshot.
final stats = manager.stats;
print('Hit rate: ${(stats.hitRate * 100).toStringAsFixed(1)}%');
print('Disk used: ${stats.totalSizeBytes ~/ 1024} KB');

Configuration reference #

CacheConfig(
  maxDiskBytes: 200 * 1024 * 1024,   // 200 MB (default)
  maxMemoryItems: 150,                // LRU item cap (default)
  maxAge: const Duration(days: 14),  // TTL (default)
  maxConcurrentDownloads: 4,         // parallel limit (default)
  maxRetries: 3,                     // retry attempts (default)
  retryBaseDelay: Duration(seconds: 1), // backoff base (default)
  downloadTimeout: Duration(seconds: 30),
  connectTimeout: Duration(seconds: 10),
  useMemoryCache: true,
  useConditionalGet: true,           // ETag / Last-Modified
  subdirectoryName: 'flutter_media_cache',
  customHeaders: {'Authorization': 'Bearer token'},
)

Architecture #

┌──────────────────────────────────────────────────────────┐
│                   MediaCacheManager                       │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────┐  │
│  │ LruMemory   │  │  CacheIndex  │  │ DownloadEngine │  │
│  │ Cache       │  │  (disk meta) │  │ (queue+retry)  │  │
│  └─────────────┘  └──────────────┘  └────────────────┘  │
└──────────────────────────────────────────────────────────┘
       ▲                   ▲                   ▲
  Memory hit           Disk hit           Network miss
  (< 1 ms)            (I/O)              (HTTP + retry)

Request flow:

  1. Check LruMemoryCache (in-process, instant).
  2. Check CacheIndex → read file from disk → promote to memory.
  3. Enqueue in DownloadEngine — deduplicated, priority-sorted.
  4. On success: persist bytes to disk, update index, promote to memory.
  5. On failure: exponential-backoff retry up to maxRetries.

Web platform notes #

On web, the package automatically switches to a memory-only caching strategy:

Feature Native (Android/iOS/desktop) Web
Storage Disk + memory Memory only
Cache persistence Survives app restart Lost on page reload
CachedImage Bytes from disk/network Bytes from memory/network
CachedVideo CacheResult.filePath available CacheResult.filePath is null

No code changes are required — the same API works everywhere. On web, CacheResult.filePath is null and CacheResult.bytes always contains the image data.


Migration from v2 #

v2 v3
CachedVideo(videoUrl: url, autoPlay: false, showControls: true) CachedVideo(videoUrl: url, builder: (ctx, r) => YourPlayer(filePath: r.filePath))

Migration from v1 #

v1 v2
FlutterMediaCache() MediaCacheManager.initialize()
CachedImage(url: ...) CachedImage(imageUrl: ...)
FlutterMediaCache.clearCache() MediaCacheManager.instance.clearAll()
No progress support getMediaWithProgress(url)
No video widget CachedVideo(videoUrl: ...)

Contributing #

PRs and issues are welcome at github.com/SwanFlutter/flutter_media_cache.


License #

MIT © SwanFlutter

1
likes
160
points
44
downloads

Documentation

API reference

Publisher

verified publisherswanflutterdev.com

Weekly Downloads

A powerful Flutter package for caching images and videos with customizable expiration time and automatic cache management.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

crypto, flutter, http, path_provider_master

More

Packages that depend on flutter_media_cache