skip to content

With Flutter's firebase_storage plugin, how do you upload a photo with putFile, show progress, and obtain a download URL afterwards?

level: juniorimportance: should knowfreq 48%

answer

  1. a Reference below the root
  2. UploadTask is also a Future
  3. snapshotEvents for progress
  4. bytesTransferred over totalBytes
  5. getDownloadURL after success

basics

~10 s

Call ref.putFile(file) on a child Reference to get an UploadTask, listen to task.snapshotEvents for bytesTransferred and totalBytes, await the task (a Future<TaskSnapshot>), then call getDownloadURL() on the uploaded reference.

solid answer

~30 s

Build a `Reference` below the bucket root, for example `FirebaseStorage.instance.ref('trips/$id/photos/p1.jpg')`, and call `putFile(file, SettableMetadata(contentType: 'image/jpeg'))`. That returns an `UploadTask`, which implements `Future<TaskSnapshot>`: awaiting it gives the final snapshot or throws a `FirebaseException` on failure or cancellation. For progress, listen to `task.snapshotEvents` and compute `bytesTransferred / totalBytes`, switching on `TaskState` (`running`, `paused`, `success`, `canceled`, `error`); `pause()`, `resume()` and `cancel()` control the task. After success, `getDownloadURL()` on the reference returns a long-lived URL to store in the trip document. On web, `putFile` is not implemented, so you upload the bytes with `putData`.

code

dart · 25 lines
dart
import 'dart:io';

import 'package:firebase_storage/firebase_storage.dart';

Future<String> uploadTripPhoto(
  String tripId,
  File photo,
  void Function(double) onProgress,
) async {
  final Reference ref = FirebaseStorage.instance
      .ref('trips/$tripId/photos/${DateTime.now().millisecondsSinceEpoch}.jpg');
  final UploadTask task = ref.putFile(
    photo,
    SettableMetadata(contentType: 'image/jpeg'),
  );
  final sub = task.snapshotEvents.listen((TaskSnapshot s) {
    if (s.totalBytes > 0) onProgress(s.bytesTransferred / s.totalBytes);
  });
  try {
    final TaskSnapshot done = await task;
    return await done.ref.getDownloadURL();
  } finally {
    await sub.cancel();
  }
}

go deeper

for a junior

Know the three steps: child reference, putFile returning an UploadTask, then getDownloadURL once the upload has finished.

for a middle

Explain that UploadTask is both a Future and a source of snapshotEvents, the five TaskState values, and pause, resume and cancel.

for a senior

Handle cancellation and failure paths, platform gaps such as web's missing putFile, and subscriptions that must be cancelled after the task.

for a principal

Decide what the app stores — download URLs or storage paths — and how uploads recover across restarts and flaky networks.

## The upload path in `firebase_storage` A group-trip planner lets travellers add photos to a shared album. With the `firebase_storage` plugin, an upload has three steps: point a `Reference` at the object's path, start an upload that returns an `UploadTask`, and — once it succeeds — ask for a download URL to store alongside the trip. ## Step 1 — a reference `FirebaseStorage.instance.ref()` returns a reference to the bucket's root, and `child(path)` descends from there; `ref('trips/lisbon/photos/p1.jpg')` does both at once. The upload guide notes that you **cannot upload to the root** of the bucket — the reference must point to a child path. ## Step 2 — the upload Three methods start an upload, each returning an `UploadTask`: | Method | Input | Notes | |---|---|---| | `putFile(File file, [SettableMetadata? metadata])` | A `dart:io` `File` that must exist | Infers the content type from the file extension; not implemented on web | | `putData(Uint8List data, [SettableMetadata? metadata])` | Bytes in memory | Works on every platform, including web | | `putString(String data, {format, metadata})` | Raw, base64, base64url or data-URL text | Uses `PutStringFormat` to decode | `SettableMetadata(contentType: 'image/jpeg')` overrides the inferred type. On web, `putFile` throws `UnimplementedError` from the platform interface — a web build reads the picked file's bytes and calls `putData` instead. ## The `UploadTask` `UploadTask` extends `Task`, which **implements `Future<TaskSnapshot>`**. That gives two ways to use it: - **`await task`** when you only need the outcome. It completes with the final `TaskSnapshot`, or throws a `FirebaseException` if the upload fails or is cancelled. - **`task.snapshotEvents`**, a `Stream<TaskSnapshot>`, when you need progress. Each snapshot carries `state`, `bytesTransferred`, `totalBytes`, `metadata` and `ref`. `TaskState` has five values: 1. `running` — emitted periodically as bytes move; drives a progress bar. 2. `paused` — after `task.pause()`. 3. `success` — the upload finished. 4. `canceled` — after `task.cancel()`. 5. `error` — the upload failed, for example on a network timeout or a rules rejection. Progress is `bytesTransferred / totalBytes`. The task also offers `pause()`, `resume()` and `cancel()`, each returning `Future<bool>`. Cancelling makes both the task's `Future` and its `snapshotEvents` stream report an error, so code awaiting the task must catch it. ## Step 3 — the download URL `Reference.getDownloadURL()` returns `Future<String>`: a long-lived URL for the object. Call it **after** the upload succeeds, typically on `snapshot.ref`, and store the URL (or just the storage path) in the trip's Firestore document so other travellers' devices can show the photo. ## Putting it together ```dart import 'dart:io'; import 'package:firebase_storage/firebase_storage.dart'; Future<String> uploadTripPhoto( String tripId, File photo, void Function(double) onProgress, ) async { final Reference ref = FirebaseStorage.instance .ref('trips/$tripId/photos/${DateTime.now().millisecondsSinceEpoch}.jpg'); final UploadTask task = ref.putFile( photo, SettableMetadata(contentType: 'image/jpeg'), ); final sub = task.snapshotEvents.listen((TaskSnapshot s) { if (s.totalBytes > 0) onProgress(s.bytesTransferred / s.totalBytes); }); try { final TaskSnapshot done = await task; return await done.ref.getDownloadURL(); } finally { await sub.cancel(); } } ``` ## Designing the album upload - **Name objects uniquely** — a timestamp or random ID in the path — so two travellers uploading `IMG_0001.jpg` do not overwrite each other. - **Store the path as well as, or instead of, the URL** in Firestore; the path lets you delete or re-derive the URL later. - **Compress before upload**; `putFile` sends the file as it is. - **Keep the task reachable** from the UI so a cancel button can call `task.cancel()`. ## Common mistakes - Calling `getDownloadURL()` right after `putFile()` without awaiting the task — the object may not exist yet. - Listening to `snapshotEvents` and never cancelling the subscription after the task ends. - Using `putFile` in a web build. - Dividing by `totalBytes` before it is known and showing `NaN` or infinity. - Catching only `TaskState.error` in the stream and forgetting that awaiting a cancelled task throws.

  • What happens to code awaiting an UploadTask when the user taps cancel?
    `task.cancel()` makes the task fail: the awaited `Future` throws a `FirebaseException` and `snapshotEvents` reports an error as well. Wrap the await in `try` and treat that exception as a user cancel rather than a failure.
  • How do you upload a picked image in a Flutter web build?
    `putFile` is not implemented on web — the platform interface throws `UnimplementedError`. Read the picked file's bytes into a `Uint8List` and call `putData(bytes, SettableMetadata(contentType: ...))`, which returns the same kind of `UploadTask` with the same progress events.

saying these in an interview costs you the question

  • putFile returns the download URL directly once it completes.
  • UploadTask is only a stream, so you cannot await it.
  • Progress is reported as a percentage field on TaskSnapshot.
  • putFile works the same way in Flutter web builds.
  • You can upload straight to the bucket's root reference.