Asset Manager Library

A lightweight, testable Flutter/Dart library for managing offline assets.
Supports downloading, verifying, caching, and syncing files from REST or GraphQL endpoints.
Ideal for apps that require reliable offline asset persistence (e.g., games, media, travel).


Features

  • Concurrent multi-file downloads
  • Retry logic with exponential backoff
  • Progress, completion, and failure callbacks
  • Local caching under app documents directory (<app-docs>/assets/<path>)
  • Syncs local DB with files on disk
  • No native dependencies
  • Works with REST or GraphQL backends (via urlResolver)

Installation

Add to your pubspec.yaml:

dependencies:
  dio: ^5.7.0
  path_provider: ^2.1.3
  path: ^1.9.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  test: ^1.25.0
  dio_http_mock_adapter: ^0.6.1
  mockito: ^5.4.4

Quick Start

Define Your AppAsset Model

Your asset model should look like:

class AppAsset {
  final String id;
  final String path;
  final String? url;
  final String? downloadUrl;
  final int? totalSize;

  AppAsset({
    required this.id,
    required this.path,
    this.url,
    this.downloadUrl,
    this.totalSize,
  });

  factory AppAsset.fromJson(Map<String, dynamic> json) => AppAsset(
    id: json['id'],
    path: json['path'],
    url: json['url'],
    downloadUrl: json['downloadUrl'],
    totalSize: json['totalSize'],
  );

  Map<String, dynamic> toJson() => {
    'id': id,
    'path': path,
    'url': url,
    'downloadUrl': downloadUrl,
    'totalSize': totalSize,
  };
}

Download Assets Using AssetLoader

await AssetLoader.downloadAssetList(
  assets: myAssets,
  urlResolver: (a) => a.url ?? a.downloadUrl ?? '',
  concurrency: 3,
  retryAttempts: 3,
  onFileProgress: (asset, p) => print("${asset.path}: ${(p * 100).toStringAsFixed(1)}%"),
  onFileCompleted: (asset, msg) => print(msg),
  onFileFailed: (asset, err) => print("Failed ${asset.path}: $err"),
  onOverallProgress: (done, total) => print("Progress: $done/$total"),
  onAllCompleted: () => print("βœ… All done!"),
);

Files are saved to <AppDocumentsDir>/assets/<asset.path>.


High-Level Use with AssetManagerDownloader

final downloader = AssetManagerDownloader()
  ..onBeforeAssetsDownloaded = () async => print("Preparing downloads...");

await downloader.downloadAssets(
  myAssets,
  onProgress: (progress, asset, msg) => print(msg),
  onFileCompleted: (asset, msg) => print('OK: ${asset.path}'),
  onFileFailed: (asset, err) => print('FAIL: ${asset.path} $err'),
  onOverallProgress: (msg) => print(msg),
  onAllCompleted: (msg) => print(msg),
  onRetry: (msg, failed) => print('Retrying ${failed.length} assets...'),
);

This approach:

  • Syncs with your local database (LocalStorage).
  • Removes missing files from the DB.
  • Tracks total bytes and overall progress.

πŸ” Check Required Assets

final allDownloaded = await downloader.isRequiredAssetsDownloaded();
if (!allDownloaded) {
  print("Some assets are missing!");
}

Checks if all assets registered in your AssetRegister are downloaded.


Project Structure

lib/
 β”œβ”€β”€ persistance/
 β”‚    └── file_handler.dart
 β”œβ”€β”€ services/
 β”‚    └── asset_management/
 β”‚         β”œβ”€β”€ asset_loader.dart
 β”‚         └── asset_downloader.dart
 └── register/
      └── asset_register.dart

Extending

  • Checksum Verification:
    Add a hash field to AppAsset and verify file integrity after download (MD5/SHA-256).
  • Resume Support:
    If your backend supports range requests, add the Range header for partial downloads.

Example: Integrate with a REST API

final response = await Dio().get("https://api.example.com/assets");
final assets = (response.data as List)
    .map((a) => AppAsset.fromJson(a))
    .toList();

await AssetLoader.downloadAssetList(
  assets: assets,
  urlResolver: (a) => a.url ?? '',
);

Utility Classes

  • FileHandler: JSON cache handler with checkFilesExist()
  • LocalStorage: Local DB wrapper for asset persistence
  • AssetLoader: Core downloader, retry logic, progress events
  • AssetManagerDownloader: High-level manager with DB + disk sync

Registering Assets

Register assets you want to use and access them from the registry.
The registry acts as a checklistβ€”ensuring all assets you need are available and downloaded to the correct paths.

You can register assets manually or automatically:

// Register a single asset
AssetRegister.instance.register("assetX", "assets/images/zz");

// Register multiple assets
AssetRegister.instance.registerAll({
  "assetX": "assets/images/zz",
  "assetY": "assets/images/yy",
});

To auto-register from a list of AppAsset objects (no manual registration required):

AssetRegister.instance.autoRegisterUsingAppAsset(myAssets);

Pros:

  • Assets are automatically registered before download.
  • With a consistent AppAsset schema (possibly stored externally), you ensure only valid assets are downloaded.

Cons:

  • If you don’t keep a record of required assets, you may lose reference in your codebase (but you can always get all registered keys).
  • All assets must conform to the AppAsset model.

License

MIT License
Β© 2025 Olamide Ogunlade