Cloud Storage can be real frustrating if you don’t know what you’re doing. Thankfully, Firebase simplifies it down to as simple as it can be. We’ll go over why you’d want to use Firebase Cloud Storage over other cloud storage services, and the different ways to go about it.

As with my other tutorials, this will be a very simplified version of using Firebase Cloud Storage. For a more comprehensive tutorial, check out the official docs.

Prerequisites

Before you can use Firebase Cloud Storage, you need to have the Firebase SDK installed. Check out how to do this in a couple minutes using the Firebase Assistant.

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if true;
    }
  }
}

You’ll also have to go to your console, set up a new project there if you haven’t already (or do it when connecting your app to Firebase with the Assistant), and intialise Cloud Storage there. It will default security rules to only allow authenticated users, but if you change it to this, it will allow all users access to the storage (make sure to change it later though).

Why use Firebase Cloud Storage?

Robust Operations

Larger upload and download operations can take time, especially for users of weaker internet connections. If network connection fails mid-operation, your operation will continue where it previously stopped.

Strong Security

You can configure security rules to only allow operations to be performed under certain conditions which you can also integrate with Firebase Authentication.

High Scalability

When your app starts going viral and more operations are being conducted, your storage can easily scale up to handle wider audiences… with a bit of money of course. You can’t get everything free.

Integrating Cloud Storage into your App

To use Firebase Cloud Storage, you’ll have to get an instance of Firebase Storage , get a reference which points to a location in the online storage bucket, then from there you can perform upload and download operations.

implementation 'com.google.firebase:firebase-storage:19.1.0'

First, add this dependency to your app-level build.gradle file.

Getting our Reference

First, get an instance of Firebase Storage.

FirebaseStorage storage = FirebaseStorage.getInstance();

Next, just like with the Realtime Database or Firestore, we’ll get a reference to the root of our storage directory.

StorageReference rootRef = storage.getReference();

Then just like in the databases, you can go lower in the tree with

StorageReference myRef = rootRef.child("images/myimagedirectory")

Extra Navigation

As with the databases again, you have the methods getParent and getRoot for up navigation.

Uploading Files to your Storage Reference

So far, we’ve treated our references like directories. With storage however, unlike the Firebase Databases, the references can point to a file’s location as well. I’ll show you what I mean.

StorageReference mountainsRef = storageRef.child("mountains.jpg");

When we upload a file to this ref, regardless of its original filename, it will be uploaded to the cloud storage as images/myimagedirectory/mountains.jpg. If we upload another file to this same reference, the new file will override the previously uploaded file.

Now that we have our file reference, we’ll now look at different ways to upload a file into this reference.

putBytes()

This method is the simplest way to upload a file that takes in a byte[] and returns an UploadTask which you can use to manage and monitor the status of the upload.

// Get the data from an ImageView as bytes
imageView.setDrawingCacheEnabled(true);
imageView.buildDrawingCache();
Bitmap bitmap = imageView.getDrawingCache();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();

UploadTask uploadTask = mountainsRef.putBytes(data);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL.
        Uri downloadUrl = taskSnapshot.getMetadata().getPath();
    }
});

In this example. we got the data from an ImageView as a byte[] and attached success and failure listeners to the UploadTask to properly handle the operation when it finishes.

The downside with putBytes() is it’s memory-intensive because it requires your app to hold the entire contents of your file in memory at once. The other 2 methods we’ll cover here are better if you want to use less memory.

putStream()

This is the most versatile method to upload to cloud storage. It takes in an InputStream and again, returns an UploadTask .

InputStream stream = new FileInputStream(new File("path/to/images/rivers.jpg"));

uploadTask = mountainsRef.putStream(stream);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL.
        Uri downloadUrl = taskSnapshot.getMetadata().getPath();
    }
});

In this example, we’re getting a FileInputStream from a directory in our local phone storage and passing it into putStream to get an UploadTask .

putFile()

This method takes a file’s Uri and once again returns an UploadTask

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));
StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());
uploadTask = riversRef.putFile(file);

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL.
        Uri downloadUrl = taskSnapshot.getMetadata().getPath();
    }
});

In this example, we’re getting a Uri from a file in our local storage and we’re passing it into putFile to get an UploadTask .

Downloading Files from your StorageReference

Just like with uploading, you start by getting a reference to the file you want to download. Once you got that, you can use one of 2 methods, all similar to the ones used to upload files. Both methods will return a DownloadTask which you can attach success and failure listeners to.

getBytes()

This is the easiest way to download files. It does have several drawbacks though. It’s more memory-intensive, and if you request a file that takes up more space than your app’s available memory, your app will crash.

StorageReference islandRef = storageRef.child("images/island.jpg");

final long ONE_MEGABYTE = 1024 * 1024;
islandRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
    @Override
    public void onSuccess(byte[] bytes) {
        // Data for "images/island.jpg" is returns, use this as needed
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle any errors
    }
});

To prevent this from happening, we pass a maximum amount of bytes we want to download into getBytes . Set this maximum to a number you know your app can handle, or use a different download method.

getFile()

This method downloads a file directly into your device’s local storage. This means that the file can then be accessed offline or shared with another app once downloaded.

islandRef = storageRef.child("images/island.jpg");

File localFile = File.createTempFile("images", "jpg");

islandRef.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
        // Local temp file has been created
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle any errors
    }
});

In case you were wondering, there is a getStream method, however I’m not great at dealing with input streams and bitmaps. If anyone knows how to do this, please leave it as a comment! It would certainly help others here as well.

Handling Errors

There are many errors that can stem from a download task, such as the file not existing or the user not having permission to access that file. Be sure to handle these errors appropriately in your OnFailureListener on your DownloadTask .

Get the Example App Source Code

As with my other tutorials, I prepared an example app for you here. This app has all the download and upload methods mentioned in this article which you can switch through by commenting and uncommenting the used methods. I certainly do hope it helps!

Newsletter

Subscribe to the Newsletter