Cloud Storage — Uploading & Downloading Files
Cloud Storage — Uploading & Downloading Files
Firebase Cloud Storage provides a powerful, scalable object-storage service for storing user-generated content such as images, videos, audio, and arbitrary binary data. In Flutter, the firebase_storage package exposes a clean Dart API that lets you upload files, monitor progress in real time, and retrieve public download URLs to display content in your UI.
firebase_storage to your pubspec.yaml and initialised Firebase in main(). Storage rules on the Firebase console must also permit reads and writes for the paths you intend to use.Getting a Storage Reference
Every operation starts with a Reference — a pointer to a specific path in your Storage bucket. Obtain one from the singleton FirebaseStorage.instance:
import 'package:firebase_storage/firebase_storage.dart';
// Root reference
final storageRef = FirebaseStorage.instance.ref();
// Reference to a nested path
final imageRef = storageRef.child('users/uid123/avatar.jpg');
// Or construct the full path directly
final fileRef = FirebaseStorage.instance.ref('uploads/documents/resume.pdf');
A Reference is just a path descriptor — it does not fetch any data. You use it as a handle to call upload, download, or metadata methods.
Uploading a File with putFile()
Use putFile() when you have a local File object (e.g. picked from the gallery via image_picker). It returns an UploadTask, which is both a Future and a Stream of progress events.
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
Future<String> uploadImageFile(File imageFile, String userId) async {
// Build a unique storage path
final fileName = '${DateTime.now().millisecondsSinceEpoch}.jpg';
final ref = FirebaseStorage.instance.ref('avatars/$userId/$fileName');
// Set optional metadata
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'uploadedBy': userId},
);
// Start the upload — returns an UploadTask
final uploadTask = ref.putFile(imageFile, metadata);
// Await completion and get a TaskSnapshot
final snapshot = await uploadTask;
// Retrieve the public download URL
final downloadUrl = await snapshot.ref.getDownloadURL();
return downloadUrl;
}
Uploading Raw Bytes with putData()
When you have a Uint8List — for example a cropped image buffer from a canvas, or data fetched from a network stream — use putData() instead of putFile(). The API is identical apart from the argument type:
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
Future<String> uploadBytes(Uint8List bytes, String path) async {
final ref = FirebaseStorage.instance.ref(path);
final metadata = SettableMetadata(contentType: 'image/png');
final uploadTask = ref.putData(bytes, metadata);
final snapshot = await uploadTask;
return await snapshot.ref.getDownloadURL();
}
Tracking Upload Progress with UploadTask
The UploadTask exposes a snapshotEvents stream that emits a TaskSnapshot on every progress tick. Each snapshot carries bytesTransferred and totalBytes, making it straightforward to display a progress indicator.
class UploadProgressWidget extends StatefulWidget {
final File file;
const UploadProgressWidget({super.key, required this.file});
@override
State<UploadProgressWidget> createState() => _UploadProgressWidgetState();
}
class _UploadProgressWidgetState extends State<UploadProgressWidget> {
double _progress = 0.0;
String _status = 'Idle';
String? _downloadUrl;
Future<void> _startUpload() async {
final ref = FirebaseStorage.instance
.ref('uploads/${DateTime.now().millisecondsSinceEpoch}.jpg');
final task = ref.putFile(widget.file);
// Listen to snapshotEvents for progress
task.snapshotEvents.listen((TaskSnapshot snapshot) {
if (!mounted) return;
setState(() {
_progress = snapshot.bytesTransferred / snapshot.totalBytes;
_status = snapshot.state == TaskState.running
? 'Uploading...'
: snapshot.state.name;
});
});
// Await the task to get the final snapshot
final finalSnapshot = await task;
final url = await finalSnapshot.ref.getDownloadURL();
if (mounted) {
setState(() {
_downloadUrl = url;
_status = 'Done';
});
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Status: $_status'),
const SizedBox(height: 8),
LinearProgressIndicator(value: _progress),
const SizedBox(height: 8),
if (_downloadUrl != null)
Image.network(_downloadUrl!, height: 200),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _startUpload,
child: const Text('Upload File'),
),
],
);
}
}
if (!mounted) return; before calling setState() inside an async callback or stream listener. The widget may have been disposed by the time the event fires, and calling setState() on an unmounted widget throws an exception.Downloading Files & Retrieving URLs
For displaying images or linking to files in your UI, the easiest approach is to retrieve a permanent download URL and pass it to a NetworkImage or open it in a browser. Use getDownloadURL() on any existing reference:
Future<void> loadAvatar(String userId) async {
try {
final ref = FirebaseStorage.instance.ref('avatars/$userId/avatar.jpg');
final url = await ref.getDownloadURL();
// url is a fully-qualified HTTPS URL — use it anywhere
print('Avatar URL: $url');
} on FirebaseException catch (e) {
if (e.code == 'object-not-found') {
print('No avatar uploaded yet.');
} else {
rethrow;
}
}
}
For downloading the raw bytes directly to memory (e.g. for processing), use getData():
Future<Uint8List?> downloadBytes(String storagePath) async {
final ref = FirebaseStorage.instance.ref(storagePath);
// Returns null if the file does not exist; pass a max byte limit
final Uint8List? data = await ref.getData(10 * 1024 * 1024); // 10 MB limit
return data;
}
Handling Errors & Task States
Upload tasks can be in one of several states exposed by TaskState: running, paused, success, canceled, and error. Wrap your task in a try/catch and inspect FirebaseException.code for actionable error messages:
storage/unauthorized— Storage rules denied the operationstorage/object-not-found— The referenced path does not existstorage/quota-exceeded— Bucket quota has been reachedstorage/canceled— The task was explicitly cancelledstorage/unknown— An unexpected server error occurred
Summary
Firebase Cloud Storage integrates naturally with Flutter through the firebase_storage package. The key workflow is: create a Reference pointing to a storage path, call putFile() or putData() to get an UploadTask, subscribe to snapshotEvents for live progress, and call getDownloadURL() on the resolved snapshot to obtain a URL ready for the UI. Always handle FirebaseException and respect Security Rules to protect user data.