Flutter Firebase Storage: Upload and Download Images

Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter Firebase Storage: Upload and Download Images, with storagereference and putfile, upload progress from snapshotevents, and getdownloadurl after completion.
Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter Firebase Storage: Upload and Download Images, with storagereference and putfile, upload progress from snapshotevents, and getdownloadurl after completion.

Coding Liquids videos show TaskState changes while an actual file crosses the network.

Subscribe on YouTube@codingliquids

Store blobs rather than Firestore fields

Cloud Storage is designed for images, video, audio, archives, and other binary objects. Keep searchable metadata and ownership fields in Firestore when the app needs queries.

Embedding large base64 strings in Firestore inflates reads and document size.

Build stable reference paths

FirebaseStorage.instance.ref creates the bucket root and child segments create paths such as users/uid/avatar.jpg. Use authenticated user IDs and generated object IDs instead of unsanitised display names.

A leading slash or inconsistent extension can create duplicate objects at unexpected paths.

flutter pub add firebase_storage

An Instagram upload timeline marks validation, progress, metadata, database linking, and orphan cleanup.

Follow me on Instagram@sagnikteaches

Upload a File with putFile

An image_picker result can provide the File, but Storage begins at ref.putFile with optional SettableMetadata. Set contentType when it cannot be inferred and await the UploadTask before requesting the final URL.

Starting several uploads for the same path makes last completion win.

The Complete Flutter Guide course thumbnail

Build production-ready Firebase Storage features

The Complete Flutter Guide turns firebase storage into maintainable app architecture, polished UI, and testable production code.

Enrol now

Declare firebase_storage before building references, UploadTasks, or download URLs.

dependencies:
  flutter:
    sdk: flutter
  firebase_storage: any

Translate snapshotEvents into progress

UploadTask.snapshotEvents emits TaskSnapshot values with bytesTransferred, totalBytes, and TaskState. Divide transferred bytes by total bytes only when the denominator is positive and handle paused, running, success, cancelled, and error states.

A progress listener that survives its screen can update disposed UI.

import 'package:firebase_storage/firebase_storage.dart';

Display the completed download URL

After success, getDownloadURL returns a tokenised HTTPS URL suitable for Image.network. Provide loadingBuilder and errorBuilder so slow or revoked URLs do not leave an empty rectangle.

Requesting the URL before putFile completes can return object-not-found.

import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';

Future<String> uploadAvatar(String uid, File file) async {
  final ref = FirebaseStorage.instance.ref('avatars/${uid}.jpg');
  final task = ref.putFile(
    file,
    SettableMetadata(contentType: 'image/jpeg'),
  );
  task.snapshotEvents.listen((snapshot) {
    final progress = snapshot.bytesTransferred / snapshot.totalBytes;
    print('Upload: ${(progress * 100).round()}%');
  });
  await task;
  return ref.getDownloadURL();
}

The LinkedIn storage analysis follows ownership rules and resumable task state beyond the happy path.

Connect on LinkedInSagnik Bhattacharya
The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Delete the object and its metadata

Call reference.delete for the blob and separately update any Firestore document that stores its URL. Decide which operation leads and how a repair job handles partial success across the two services.

Deleting only the database row leaves paid storage orphaned.

StreamBuilder<TaskSnapshot>(
  stream: uploadTask.snapshotEvents,
  builder: (context, snapshot) {
    final task = snapshot.data;
    final progress = task == null || task.totalBytes == 0
        ? 0.0
        : task.bytesTransferred / task.totalBytes;
    return LinearProgressIndicator(value: task?.state == TaskState.running ? progress : null);
  },
)

Enforce ownership in Storage Rules

Rules can inspect request.auth, path variables, content type, and size before allowing writes. Restrict users to their own folder and cap accepted image size and MIME types.

A client-side file extension check can be bypassed and must not replace server rules.

final avatarRef = FirebaseStorage.instance.ref().child('users/$uid/avatar.jpg');
final downloadUrl = await avatarRef.getDownloadURL();
final avatar = Image.network(
  downloadUrl,
  errorBuilder: (_, __, ___) => const Icon(Icons.person),
);
await avatarRef.delete();

UploadTask also supports pause, resume, and cancel, which are useful for large media on unreliable connections. Keep the task object in the upload screen, disable duplicate selection while it runs, and map paused or cancelled work to explicit controls. A resumed task continues transferring to the same StorageReference rather than creating a second object path. Test cancellation before requesting getDownloadURL because a cancelled upload has no completed object to resolve.

Make upload state resumable and accountable

Object paths should encode stable ownership, not a display name supplied by the user. A path such as users/$uid/avatars/current.jpg is easy to protect with Storage rules, while an unsanitised filename can create collisions or surprising nested paths. Generate a unique ID for retained uploads and store the resulting path in the related database record. Keep the path as the durable reference; a download URL can change or be revoked and should not be treated as the object’s identity.

Validate size and media type before starting the transfer, then enforce equivalent limits in rules or trusted backend processing. Client checks improve feedback but can be bypassed. Set accurate content-type metadata and do not trust the extension alone when the file will be rendered or redistributed. Images may need server-side decoding, dimension limits, malware checks, and derivative generation before the application treats them as published content.

Listen to the upload task for transferred bytes, total bytes, pause, resume, success, cancellation, and failure. Progress is indeterminate when the total is unavailable, and the widget must tolerate that state. Retain the task outside the transient build method so rebuilding cannot start another upload. When a route closes, decide whether the transfer should continue under repository ownership or be cancelled deliberately; merely dropping the progress listener leaves the network operation alive.

Test object-not-found, unauthorized, canceled, quota-exceeded, an offline interruption, and an app restart during a large upload. If database metadata is written after the object, plan cleanup for an object whose second step fails. If metadata is written first, mark it pending until Storage confirms success. Rules tests should prove that one account cannot read or overwrite another account’s path. Diagnostics can include the bucket, logical path pattern, byte count, task state, and error code, but should omit signed URLs and user filenames that reveal private content.

Downloads deserve the same lifecycle discipline as uploads. Set a memory ceiling before using an in-memory byte API; large objects should stream to a file or platform-appropriate destination. Verify the object’s metadata and expected owner before displaying untrusted content, and render failures without retaining a stale image under the new record. Cache invalidation should key off an object generation, versioned path, or updated metadata rather than a permanent URL alone. When replacing an avatar, upload the new object first, update the profile reference, and delete the previous object last so a failed transfer never leaves the account with no usable image.

App Check can reduce abuse from unauthorised clients, but it complements rather than replaces Storage rules and authentication. Roll it out with monitoring before enforcement so legitimate platform or debug builds are not surprised. For local development, use supported debug providers or emulators instead of weakening production rules. Periodic backend cleanup can identify abandoned pending objects, but deletion should require an age threshold and proof that no live database record references the path.

Common mistakes

  • Store blobs rather than Firestore fields: In Firebase Storage uploads, embedding large base64 strings in Firestore inflates reads and document size; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.
  • Build stable reference paths: In Firebase Storage uploads, a leading slash or inconsistent extension can create duplicate objects at unexpected paths; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.
  • Upload a File with putFile: In Firebase Storage uploads, starting several uploads for the same path makes last completion win; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.
  • Translate snapshotEvents into progress: In Firebase Storage uploads, a progress listener that survives its screen can update disposed UI; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.
  • Display the completed download URL: In Firebase Storage uploads, requesting the URL before putFile completes can return object-not-found; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.
  • Delete the object and its metadata: In Firebase Storage uploads, deleting only the database row leaves paid storage orphaned; inspect this Firebase Storage uploads cause before changing another Firebase Storage uploads widget.

Frequently asked questions

How does store blobs rather than Firestore fields work in Firebase Storage uploads?

For Firebase Storage uploads, the starting rule is that cloud Storage is designed for images, video, audio, archives, and other binary objects. Apply this Firebase Storage uploads rule first because store blobs rather than Firestore fields determines whether the Firebase Storage uploads pattern fits.

Why does upload a File with putFile matter for Firebase Storage uploads?

In Firebase Storage uploads, set contentType when it cannot be inferred and await the UploadTask before requesting the final URL. Keeping upload a File with putFile at the Firebase Storage uploads call site exposes the Firebase Storage uploads return value directly.

What failure should I test first in Firebase Storage uploads?

First reproduce the Firebase Storage uploads case where requesting the URL before putFile completes can return object-not-found. The working sequence awaits upload completion before requesting a URL, and a missing object remains an explicit error rather than stale UI.

How can I verify Firebase Storage uploads before release?

Exercise enforce ownership in Storage Rules with real Firebase Storage uploads inputs on every shipped platform. Inspect the final Firebase Storage uploads callback or output; a successful Firebase Storage uploads button tap alone is not proof.

Further reads

Connect Firebase Storage uploads to the surrounding Flutter stack through these published tutorials:

Sources: FlutterFire and Firebase documentation; Firebase Storage uploads examples verified against current stable Flutter.