Using Cloud Storage

To start using the Cloud Storage package within your project, import it at the top of your project files:

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

Before using Storage, you must first have ensured you have initialized FlutterFire.

To create a new Storage instance, call the instance getter on FirebaseStorage:

firebase_storage.FirebaseStorage storage =
firebase_storage.FirebaseStorage.instance;

By default, this allows you to interact with Storage using the default Firebase App used whilst installing FlutterFire on your platform. If however you'd like to use Storage with a secondary Firebase App, use the instanceFor method:

FirebaseApp secondaryApp = Firebase.app('SecondaryApp');
firebase_storage.FirebaseStorage storage =
firebase_storage.FirebaseStorage.instanceFor(app: secondaryApp);

It is also possible to specify a non-default Storage bucket which you may have created in your Firebase project (see below). To connect to a different bucket, provide the name to the instanceFor method:

firebase_storage.FirebaseStorage storage =
firebase_storage.FirebaseStorage.instanceFor(
bucket: 'secondary-storage-bucket');

If no app argument is provided to the instanceFor method, the default app will be used.

Create additional storage buckets

If you haven't already done so, you first need to create the default storage bucket on the Firebase console.

Firebase provides the ability to create multiple buckets per project. Creating additional storage buckets is recommended to separate unrelated files for your project (such as database backups) however are not required to successfully use Firebase Storage.

You can create a new bucket by visiting the Firebase console. For more information, visit the official documentation.

References

A reference is a pointer to a file within your specified storage bucket. This can be a file that already exists, or one that does not exist.

To create a reference, use the ref method on a FirebaseStorage instance:

firebase_storage.Reference ref =
firebase_storage.FirebaseStorage.instance.ref('/notes.txt');

A reference can also be a nested file or directory. Provide the full path or use the child method on the returned reference:

firebase_storage.Reference ref = firebase_storage.FirebaseStorage.instance
.ref('images/defaultProfile.png');
// or
firebase_storage.Reference ref = firebase_storage.FirebaseStorage.instance
.ref()
.child('images')
.child('defaultProfile.png');

When no argument to ref is provided, the root of the storage bucket will be used as the reference.

Listing files & directories

Firebase provides the ability to list the files and directories within a directory. There are two methods available which provide this ability; list & listAll. Both methods return a ListResult which contains any files, directories and pagination tokens from the request.

For example, to view all files and directories within the root of the default storage bucket:

Future<void> listExample() async {
firebase_storage.ListResult result =
await firebase_storage.FirebaseStorage.instance.ref().listAll();
result.items.forEach((firebase_storage.Reference ref) {
print('Found file: $ref');
});
result.prefixes.forEach((firebase_storage.Reference ref) {
print('Found directory: $ref');
});
}

The items property represents files within the bucket, and the prefixes property represents nested directories.

In cases where you have a large volume of files and directories, calling listAll may take a long time to return all results. In this case, calling list and limiting the results may result in a better user experience:

Future<void> listExample() async {
firebase_storage.ListResult result = await firebase_storage
.FirebaseStorage.instance
.ref()
.list(firebase_storage.ListOptions(maxResults: 10));
// ...
}

The ListOptions instance provided to list allows us to limit the number of results returned. It also accepts an optional pageToken argument which can be used to paginate any additional results not returned in the listing, for example:

Future<void> listExample() async {
firebase_storage.ListResult result = await firebase_storage
.FirebaseStorage.instance
.ref()
.list(firebase_storage.ListOptions(maxResults: 10));
if (result.nextPageToken != null) {
firebase_storage.ListResult additionalResults = await firebase_storage
.FirebaseStorage.instance
.ref()
.list(firebase_storage.ListOptions(
maxResults: 10,
pageToken: result.nextPageToken,
));
}
}

Download URLs

In many use-cases, you may wish to display images stored on a storage bucket within your application, using Firebase as a scalable and cost-effective Content Distribution Network (CDN). Firebase allows you to retrieve a long-lived download URL which can be used. To request a download URL, call the getDownloadURL method on a reference:

Future<void> downloadURLExample() async {
String downloadURL = await firebase_storage.FirebaseStorage.instance
.ref('users/123/avatar.jpg')
.getDownloadURL();
// Within your widgets:
// Image.network(downloadURL);
}

Uploading Files

FlutterFire supports uploading via variety of ways to Firebase Storage, such a raw string values or on-device files.

File uploads

To upload a file, you must first create an absolute path to it's on-device location. For example, if a file exists within the application's documents directory, we can use the official path_provider package to generate a file path:

import 'package:path_provider/path_provider.dart';
Future<void> uploadExample() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
String filePath = '${appDocDir.absolute}/file-to-upload.png';
// ...
// e.g. await uploadFile(filePath);
}

Once your absolute path has been created, it can be passed as a File instance to the putFile method:

Future<void> uploadFile(String filePath) async {
File file = File(filePath);
try {
await firebase_storage.FirebaseStorage.instance
.ref('uploads/file-to-upload.png')
.putFile(file);
} on firebase_core.FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}
}

To learn more about tasks, view the Handling Tasks documentation.

Upload from a String

It is possible to upload a raw, base64, base64url, or data_url encoded string to Firebase Storage using the putString method. For example, to upload a text string encoded as a Data URL:

Future<void> uploadString() async {
String dataUrl = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==';
try {
await firebase_storage.FirebaseStorage.instance
.ref('uploads/hello-world.text')
.putString(dataUrl, format: firebase_storage.PutStringFormat.dataUrl);
} on firebase_core.FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}
}

To learn more about tasks, view the Handling Tasks documentation.

Uploading raw data

FlutterFire also supports uploading lower-level typed data in the form of a Uint8List for those cases where uploading a string or File is not practical. In this case, call the putData method with your data:

import 'dart:convert' show utf8;
import 'dart:typed_data' show Uint8List;
Future<void> uploadData() async {
String text = 'Hello World!';
List<int> encoded = utf8.encode(text);
Uint8List data = Uint8List.fromList(encoded);
firebase_storage.Reference ref =
firebase_storage.FirebaseStorage.instance.ref('uploads/hello-world.text');
try {
// Upload raw data.
await ref.putData(data);
// Get raw data.
Uint8List downloadedData = await ref.getData();
// prints -> Hello World!
print(utf8.decode(downloadedData));
} on firebase_core.FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}
}

To learn more about tasks, view the Handling Tasks documentation.

Adding Metadata

When uploading a file, you can specify metadata such as the content type, cache control and any custom properties for the file.

Whilst calling one of the upload methods, provide a SettableMetadata instance:

Future<void> uploadFileWithMetadata(String filePath) async {
File file = File(filePath);
// Create your custom metadata.
firebase_storage.SettableMetadata metadata =
firebase_storage.SettableMetadata(
cacheControl: 'max-age=60',
customMetadata: <String, String>{
'userId': 'ABC123',
},
);
try {
// Pass metadata to any file upload method e.g putFile.
await firebase_storage.FirebaseStorage.instance
.ref('uploads/file-to-upload.png')
.putFile(file, metadata);
} on firebase_core.FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}
}

If a file is already present on the storage bucket, you can request the metadata by calling getMetadata:

Future<void> getMetadataExample() async {
firebase_storage.FullMetadata metadata = await firebase_storage
.FirebaseStorage.instance
.ref('uploads/file-to-upload.png')
.getMetadata();
// As set in previous example.
print(metadata.customMetadata['userId']);
}

Downloading Files

To download a file to the local device, you can call the writeToFile method on any storage bucket reference. The location of where the file will be downloaded to is determined by the absolute path of the File instance provided, for example:

import 'package:path_provider/path_provider.dart';
Future<void> downloadFileExample() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
File downloadToFile = File('${appDocDir.absolute}/download-logo.png');
try {
await firebase_storage.FirebaseStorage.instance
.ref('uploads/logo.png')
.writeToFile(downloadToFile);
} on firebase_core.FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}
}

If a file already exists at the provided path, it will be overwritten.

To learn more about tasks, view the Handling Tasks documentation.

Handling Tasks

The uploading and downloading examples in the documentation all return a UploadTask or DownloadTask. Tasks provide the ability to control how the file is being uploaded/downloaded and provides metadata on the state of the task (e.g. progress status).

The simplest way to hook into when a task has completed is to await the task, for example:

Future<void> handleTaskExample1() async {
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance
.ref('uploads/hello-world.txt')
.putString('Hello World');
try {
// Storage tasks function as a Delegating Future so we can await them.
firebase_storage.TaskSnapshot snapshot = await task;
print('Uploaded ${snapshot.bytesTransferred} bytes.');
} on firebase_core.FirebaseException catch (e) {
// The final snapshot is also available on the task via `.snapshot`,
// this can include 2 additional states, `TaskState.error` & `TaskState.canceled`
print(task.snapshot);
if (e.code == 'permission-denied') {
print('User does not have permission to upload to this reference.');
}
// ...
}
}

Although this can satisfy most use-cases, for larger files it may be a better user experience if a progress indicator can be shown. Tasks can also provide a stream of events, which emits a TaskSnapshot each time a notable event occurs (e.g. upload progress). The snapshot provides information about the state of the task along with the amount of bytes that have been processed:

Future<void> handleTaskExample2(String filePath) async {
File largeFile = File(filePath);
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance
.ref('uploads/hello-world.txt')
.putFile(largeFile);
task.snapshotEvents.listen((firebase_storage.TaskSnapshot snapshot) {
print('Task state: ${snapshot.state}');
print(
'Progress: ${(snapshot.totalBytes / snapshot.bytesTransferred) * 100} %');
}, onError: (firebase_core.FirebaseException e) {
// The final snapshot is also available on the task via `.snapshot`,
// this can include 2 additional states, `TaskState.error` & `TaskState.canceled`
print(task.snapshot);
if (e.code == 'permission-denied') {
print('User does not have permission to upload to this reference.');
}
});
// We can still optionally use the Future alongside the stream.
try {
await task;
print('Upload complete.');
} on firebase_core.FirebaseException catch (e) {
if (e.code == 'permission-denied') {
print('User does not have permission to upload to this reference.');
}
// ...
}
}

The state property of a snapshot indicates a TaskState. The state can be one of the following values on stream snapshots:

  • TaskState.running - Indicates the task is currently running.
  • TaskState.paused - Indicates the task is paused.
  • TaskState.success - The task has successfully completed and no more events will be sent.

Using both snapshotEvents and the task delegated Future together, or individually is supported - use whichever combination works for your application.

Pause, resume and cancel

It is possible to pause/resume or cancel a task, by calling the pause, resume or cancel methods:

Future<void> handleTaskExample3(String filePath) async {
File largeFile = File(filePath);
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance
.ref('uploads/hello-world.txt')
.putFile(largeFile);
// Pause the upload.
bool paused = await task.pause();
print('paused, $paused');
// Resume the upload.
bool resumed = await task.resume();
print('resumed, $resumed');
// Cancel the upload.
bool cancelled = await task.cancel();
print('cancelled, $cancelled');
// ...
}

When calling pause or resume, any snapshotEvents listeners will be triggered with a new TaskState.

However, calling cancel will cause the task to fail. Any Future or Stream listeners will receive a FirebaseException instance with a canceled code instead, for example:

Future<void> handleTaskExample4(String filePath) async {
File largeFile = File(filePath);
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance
.ref('uploads/hello-world.txt')
.putFile(largeFile);
// Via a Stream
task.snapshotEvents.listen((firebase_storage.TaskSnapshot snapshot) {
// Handle your snapshot events...
}, onError: (e) {
// Check if cancelled by checking error code.
if (e.code == 'canceled') {
print('The task has been canceled');
}
// Or, you can also check for cancellations via the final task.snapshot state.
if (task.snapshot.state == firebase_storage.TaskState.canceled) {
print('The task has been canceled');
}
// If the task failed for any other reason then state would be:
print(firebase_storage.TaskState.error);
});
// Cancel the upload.
bool cancelled = await task.cancel();
print('cancelled? $cancelled');
// Or a Task Future (or both).
try {
await task;
} on firebase_core.FirebaseException catch (e) {
// Check if cancelled by checking error code.
if (e.code == 'canceled') {
print('The task has been canceled');
}
// Or, you can also check for cancellations via the final task.snapshot state.
if (task.snapshot.state == firebase_storage.TaskState.canceled) {
print('The task has been canceled');
}
// If the task failed for any other reason then state would be:
print(firebase_storage.TaskState.error);
// ...
}
}