Storage Manager by MIS Develop

storage_manager

pub package

storage_manager gives one static API, StorageProvider, for two kinds of storage:

  • Firebase Cloud Storage (firebase_storage): upload, download, list and delete files.
  • Local storage (shared_preferences): keep strings, bytes (images, videos, audio) and JSON on the device.

It also includes an image/video picker (gallery or camera) and an upload progress dialog. You can replace both with your own UI.

Features

  • Upload a String, Uint8List, List<int> or JSON map and get its download URL back.
  • Download a file as bytes, as UTF-8 text or as decoded JSON.
  • Pick one or more images, or a video, then upload them in a single call.
  • Show an optional upload progress dialog, or plug in your own.
  • Use the same save / get / remove calls for local storage.

Compatibility

storage_manager firebase_storage Dart / Flutter
0.7.x 12.x and 13.x Dart ≥ 3.6, Flutter ≥ 3.27
0.6.x 12.x Dart ≥ 3.4, Flutter ≥ 3.27

The supported platforms are Android, iOS, macOS and Web, which are the platforms firebase_storage and image_picker support.

Getting started

  1. Configure Firebase in your app, for example with the FlutterFire CLI:

    dart pub global activate flutterfire_cli
    flutterfire configure
    
  2. Add the package:

    flutter pub add storage_manager
    
  3. Import it:

    import 'package:storage_manager/storage_manager.dart';
    

image_picker needs camera and photo permissions on iOS and macOS. See the image_picker setup.

Usage

Upload to Cloud Storage

For remote uploads, path is a folder prefix. The file is stored at path + fileName + extensionFormat, and fileName defaults to the current timestamp. Each call returns the download URL.

// Text, bytes or a JSON map (maps are uploaded as JSON text).
final url = await StorageProvider.save('notes/', 'Hello', fileName: 'greeting', extensionFormat: '.txt');
final jsonUrl = await StorageProvider.save('configs/', {'theme': 'dark'}, extensionFormat: '.json');
final bytesUrl = await StorageProvider.save('files/', bytes, extensionFormat: '.pdf');

// An XFile from image_picker. It is stored as `<original-name>-<timestamp><extension>`.
final imageUrl = await StorageProvider.saveImage(xFile, 'users/42/avatars/'); // default '.png'
final videoUrl = await StorageProvider.saveVideo(xFile, 'users/42/videos/'); // default '.mp4'
final fileUrl = await StorageProvider.saveBytes(xFile, 'uploads/', extensionFormat: '.bin');

Download from Cloud Storage

final Uint8List? image = await StorageProvider.getImage('users/42/avatars/me.png');
final Uint8List? video = await StorageProvider.getVideo('users/42/videos/clip.mp4');
final String? text = await StorageProvider.getString('notes/greeting.txt');
final Map<String, dynamic>? json = await StorageProvider.getJson('configs/app.json');

// Generic form; `type` picks the decoder (string by default).
final dynamic value = await StorageProvider.get('configs/app.json', type: StorageType.json);

Remove

await StorageProvider.remove('notes/greeting.txt');
await StorageProvider.removeUrl(downloadUrl);
await StorageProvider.remove('local-key', isLocal: true);

Local storage

For local storage, path is the key the value is stored under.

await StorageProvider.save('profile', {'name': 'Ana'}, toLocalStorage: true);
await StorageProvider.saveLocalString('token', 'abc');
await StorageProvider.saveLocalImage('avatar', imageBytes);
await StorageProvider.saveLocalVideo('intro', videoBytes);
await StorageProvider.saveLocalJson('settings', {'sound': true});

final String? token = await StorageProvider.getLocalString('token');
final Uint8List? avatar = await StorageProvider.getLocalImage('avatar');
final Map<String, dynamic>? settings = await StorageProvider.getLocalJson('settings');

Bytes are stored as base64 strings in SharedPreferences. That is fine for small files; for large media, use files or a database.

Pick and upload images or videos

// 1. Pick. With no `source`, a bottom sheet asks for camera or gallery, so `context` is required.
if (await StorageProvider.selectAssets(context: context, maxImagesCount: 5)) {
  final List<XFile>? picked = StorageProvider.selectedAssets;

  // 2. Upload what was picked and get the download URLs back.
  final List<String> urls = await StorageProvider.uploadSelectedAssets('products/123/');
}

// Or do both in one call:
final List<String> urls = await StorageProvider.selectAndUpload(
  'products/123/',
  source: ImageSource.gallery,
  context: context,
  showProgress: true,
);

// Upload specific files instead of the picked ones:
final List<String> videoUrls = await StorageProvider.uploadSelectedAssets(
  'clips/',
  selectedImages: [videoFile],
  isVideo: true,
);

selectAssets returns false when the user cancels. If maxImagesCount is greater than 1 and the source is the gallery, the user can select several images.

Customization

Set the context, the progress behaviour, the source picker and the progress dialog once, for example in your root widget:

StorageProvider.configure(
  context: context,
  showProgress: true,
  getImageSource: () => showDialog<ImageSource>(
    context: context,
    builder: (context) => SimpleDialog(
      title: const Text('Select image source'),
      children: [
        ListTile(
          leading: const Icon(Icons.camera_alt),
          title: const Text('Camera'),
          onTap: () => Navigator.pop(context, ImageSource.camera),
        ),
        ListTile(
          leading: const Icon(Icons.photo),
          title: const Text('Gallery'),
          onTap: () => Navigator.pop(context, ImageSource.gallery),
        ),
      ],
    ),
  ),
  showDataUploadProgress: (uploadTask) => showDialog<void>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Uploading...'),
      content: ProgressFromUploadTask(task: uploadTask, onDone: () => Navigator.pop(context)),
    ),
  ),
);

Each setting can also be changed on its own with StorageProvider.context, StorageProvider.showProgress, StorageProvider.getImageSource and StorageProvider.customUploadProgressIndicator.

For lower-level access, use FireUploader (Cloud Storage) and DataPersistor (SharedPreferences) directly.

Web: CORS for downloads and images

In the browser, getImage / getString / getJson (which download bytes) and Image.network on Storage URLs need your bucket to allow your app's origin. Create a cors.json file:

[
  {
    "origin": ["https://your-app.web.app", "http://localhost:8080"],
    "method": ["GET"],
    "maxAgeSeconds": 3600
  }
]

Then apply it to the bucket:

gcloud storage buckets update gs://YOUR_BUCKET --cors-file=cors.json

Additional information

  • This package assumes Firebase is initialized and that your Storage security rules allow the paths you use.
  • Example app: example/
  • Issues and contributions: GitHub

Libraries

storage_manager
Upload, download and delete files in Firebase Cloud Storage, and persist strings, bytes and JSON locally with SharedPreferences, through a single StorageProvider API.