With Flutter's firebase_storage plugin, how do you upload a photo with putFile, show progress, and obtain a download URL afterwards?
answer
- a Reference below the root
- UploadTask is also a Future
- snapshotEvents for progress
- bytesTransferred over totalBytes
- getDownloadURL after success
basics
~10 sCall 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 sBuild 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 linesimport '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
Know the three steps: child reference, putFile returning an UploadTask, then getDownloadURL once the upload has finished.
Explain that UploadTask is both a Future and a source of snapshotEvents, the five TaskState values, and pause, resume and cancel.
Handle cancellation and failure paths, platform gaps such as web's missing putFile, and subscriptions that must be cancelled after the task.
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.