Firebase for Android: File Storage

Firebase for Android: File Storage

Firebase Storage provides facility to upload any file like image, video, audio, etc without using server side code.

Firebase Storage is designed specifically for scale, security, and network resiliency.

  • Scale. Every file uploaded is backed by Google Cloud Storage, which scales to petabytes.
  • Security. Files can be secured to specific users or sets of users using Storage Security Rules.
  • Network Resiliency. Uploads and downloads are automatically retried in the case of poor network connections, so you don’t have to keep track of them yourself.

Upload file to Firebase Storage is as simple as:

StorageReference storageRef = FirebaseStorage.getInstance().reference().child("folderName/file.jpg");
Uri file = Uri.fromFile(new File("path/to/folderName/file.jpg"));
storageRef.putFile(file)
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // get a URL to the uploaded content
        @SuppressWarnings("VisibleForTests") Uri downloadUrl = taskSnapshot.getDownloadUrl();
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // handle unsuccessful uploads
    }
});

First of all we have to connect our project to Firebase and then add the required dependencies. For doing this just follow below steps.

To configure the project to use the Firebase platform, open the Firebase Assistant window by clicking on Tools > Firebase. Now from the assistant go to Authentication and click Email and password aithentication. Next, press the Connect to Firebase button and make sure that the Create new Firebase project option is selected. Finaly, click Add Firebase Authentication.

android_firebase_setup.png

Repeat the same steps for Storage.

Now you have connected Firebase platform with Authentication and Storage. Also you can manage your Firebase data from Firebase console.

Before you can begin using Firebase Storage, you'll need to either make sure your user is authenticated, or change the authentication requirement rules in the Firebase console to allow unauthenticated users to access and upload files. To keep things simple, we'll do the latter. Let's start by going into the Storage section of Firebase by selecting Storage in the left navigation column.

Next, you'll notice there are two tabs at the top of the Storage screen: Files and Rules.

Select the Rules tab, and on the line allow read, write: if request.auth != null;, change != to == and click the PUBLISH button.

Now any user of your app should be able to upload or download files from your Firebase back-end. While this is not ideal for a production environment, it will make learning about Firebase Storage a lot easier without having to dig into authentication code.

Manually Uploading Files

While being able to upload files from an app is great, sometimes you'll simply want to store files somewhere that can be easily accessed and pulled down into your app. This is where being able to manually upload files from the Firebase Console comes into play. Under the Files tab, you'll see a blue button titled Upload File.

Click that and select the file you want to upload, and it will appear in your Firebase Storage.

Downloading file

Now that you've got a file stored in Firebase Storage, let's go ahead and pull it down into an app. We'll use a simple layout in our MainActivity that contains an ImageView with an id of image.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/ivImage"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

In order to access your Firebase Storage files, you'll need to first get a reference to the FirebaseStorage object, and then create a StorageReference to your project's URL and the file that you want to download. You can find your project's URL at the top of the Files section of Storage in the Firebase Console.

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReferenceFromUrl("gs://fileX.appspot.com").child("image.jpg");

Next, you can create a File object and attempt to load the file you want by calling getFile on your StorageReference with the new File object passed as a parameter. Since this operation happens asynchronously, you can add an OnSuccessListener and OnFailureListener to your call in order to handle either contingency.

try {
    final File localFile = File.createTempFile("image", "jpg");
    storageRef.getFile(localFile).addOnSuccessListener(
        new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
        @Override
        public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
            Bitmap bitmap = BitmapFactory.decodeFile(localFile.getAbsolutePath());
            ivImage.setImageBitmap(bitmap);
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {}
    });
} catch (IOException e ) {}

In onSuccess() from OnSuccessListener, you can take the FileDownloadTask.TaskSnapshot object and retrieve the file, which is where we will set the image to our ImageView.

If you only need to download the file as a byte[] and don't need it as a file, which is the more likely case when loading an image into an ImageView, then you can retrieve the bytes in a similar fashion. But since this process will load the whole file into memory, and if we request a file larger than the available memory, it will crash. To handle this memory issue, getBytes() takes the maximum amount of bytes to download.

final long ONE_MEGABYTE = 1024 * 1024;
storageRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
    @Override
    public void onSuccess(byte[] bytes) {
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        ivImage.setImageBitmap(bitmap);
    }
});

There may be situations where you don't need the actual data for a stored file, but rather will want the URL. You can do this in a similar fashion as the last two examples by using the getDownloadUrl() method on your StorageReference, which will give you a Uri pointing to the file's location.

storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
    @Override
    public void onSuccess(Uri uri) {
        Log.d(TAG, "uri: " + uri.toString());
    }
});

Uploading file

Uploading a Byte Array. As with downloading, you will need to get a reference to the FirebaseStorage object and create a reference to your new file's storage location as a StorageReference. For this example, we will show ic_launcher.png in our ImageView, and then we'll upload that as an array of bytes.

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageReference = storage.getReferenceFromUrl("gs://fileX.appspot.com").child("ic_launcher.png");

Next, we will need to get a byte array from the image stored in memory via the ImageView. This is done by retrieving it as a Bitmap, compressing it into a ByteArrayOutputStream, and then turning that into a byte[].

// get image from ImageView
ivImage.setDrawingCacheEnabled(true);
ivImage.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), 
    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
ivImage.layout(0, 0, ivImage.getMeasuredWidth(), ivImage.getMeasuredHeight());
ivImage.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(ivImage.getDrawingCache());

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
byte[] data = outputStream.toByteArray();

// get image from Drawable
Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.image);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
icon.compress(Bitmap.CompressFormat.JPEG, 80, bos);
byte[] data = outputStream.toByteArray();

Finally, you can create an UploadTask by calling putBytes(byte[]) to upload your image to Firebase. This UploadTask can also have an OnSuccessListener and OnFailureListener associated with it.

UploadTask uploadTask = storageReference.putBytes(data);

uploadTask.
addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        @SuppressWarnings("VisibleForTests") Uri downloadUrl = taskSnapshot.getDownloadUrl();
        Log.d(TAG, "Uploaded . You can use this download url " + downloadUrl);
    }
})
.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        @SuppressWarnings("VisibleForTests") long bytes = taskSnapshot.getBytesTransferred();
        Log.d(TAG, "Bytes uploaded: " + bytes);
        //int progress = (int)(100.0 * (taskSnapshot.getBytesTransferred() / taskSnapshot.getTotalByteCount()));
        //progressBar.setProgress(progress);
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {}
});

When you check the Firebase Console Storage page, you should see ic_launcher.png in your list of files.

Uploading from an InputStream or File. Now that you know how to upload a byte array, the other two types of uploads should be fairly intuitive. Let's say we have a text file named test.txt in our raw resources folder. We can read this into an InputStream and then upload it by using the putStream(InputStream) method of StorageReference.

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageReference = storage.getReferenceFromUrl("gs://fileX.appspot.com").child("test.txt");

// from raw resource
InputStream stream = getResources().openRawResource(R.raw.test);

// from drawable
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.image);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 80, bos);
InputStream stream = new ByteArrayInputStream(bos.toByteArray());

UploadTask uploadTask = storageReference.putStream(stream);
uploadTask
.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        @SuppressWarnings("VisibleForTests") long bytes = taskSnapshot.getBytesTransferred();
        Log.d(TAG, "Bytes uploaded: " + bytes);
        //int progress = (int)(100.0 * (taskSnapshot.getBytesTransferred() / taskSnapshot.getTotalByteCount()));
        //progressBar.setProgress(progress);
    }
})
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        @SuppressWarnings("VisibleForTests") Uri downloadUrl = taskSnapshot.getDownloadUrl();
        Log.d(TAG, "Uploaded . You can use this download url " + downloadUrl);
        //progressBar.setVisibility(View.GONE);
    }
});

Uploading an existing file is just as easy: simply get a reference to the file and call putFile(Uri) with a URI pointing to your file. For this example, we'll just create an empty temp file in our code.

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageReference = storage.getReferenceFromUrl("gs://fileX.appspot.com").child("test2.txt");

File file = null;
try {
    file = File.createTempFile("test2", "txt");
} catch( IOException e ) {}

UploadTask uploadTask = storageReference.putFile(Uri.fromFile(file));

Deleting file

As with uploading, you will need to get a reference to the FirebaseStorage object and create a reference to your existed file's storage location as a StorageReference.

Deleting a file:

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageReference = storage.getReferenceFromUrl("gs://fileX.appspot.com").child("image.jpg");

imageRef.delete()
.addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "Deleted");
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        Log.d(TAG, "Failed");
    }
});

Handling the Android Activity lifecycle

As any Android developer can attest, sometimes the Android activity lifecycle can cause unexpected issues. One of the common sources of problems is listeners that last longer than their parent Activity, which may be the case for success/failure listeners attached to a Firebase Storage task.

If an Activity is destroyed and recreated (such as on screen rotation) while a task is occurring, you may end up with a NullPointerException when the task has completed. To avoid this, you will want to save your StorageReference as a String in your out state Bundle in the onSaveInstanceState(Bundle) method, and then retrieve it and add success listeners to each FileDownloadTask or FileUploadTask associated with that StorageReference.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    if (storageReference != null) {
        outState.putString(EXTRA_STORAGE_REFERENCE_KEY, storageReference.toString());
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    final String stringRef = savedInstanceState.getString(EXTRA_STORAGE_REFERENCE_KEY);

    if (stringRef == null) {
        return;
    }

    storageReference = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);
    List<FileDownloadTask> tasks = storageReference.getActiveDownloadTasks();
    for(FileDownloadTask task : tasks) {
        task.addOnSuccessListener(this, new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
                Log.d(TAG, "download successful!");
            }
        });
    }
}

How to upload file from Gallery

Following is simple example how to upload image from Gallery.

First of all, we should create a layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Choose Image"
        android:id="@+id/btnChoose"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Upload Image"
        android:id="@+id/btnUpload"/>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:id="@+id/ivImage"/>
</LinearLayout>

Next, we create MainActivity.java.

public class MainActivity extends AppCompatActivity {
    Button btnChoose, btnUpload;
    ImageView ivImage;
    int PICK_IMAGE_REQUEST = 111;
    Uri filePath;
    ProgressDialog pd;

    // creating reference to firebase storage
    FirebaseStorage storage = FirebaseStorage.getInstance();
    // change the url according to your firebase app
    StorageReference storageRef = storage.getReferenceFromUrl("gs://fileX.appspot.com");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnChoose = (Button)findViewById(R.id.btnChoose);
        btnUpload = (Button)findViewById(R.id.btnUpload);
        ivImage = (ImageView)findViewById(R.id.ivImage);

        pd = new ProgressDialog(this);
        pd.setMessage("Uploading...");

        btnChoose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_PICK);
                startActivityForResult(Intent.createChooser(intent, "Select image"), PICK_IMAGE_REQUEST);
            }
        });

        btnUpload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(filePath != null) {
                    pd.show();

                    StorageReference childRef = storageRef.child("image.jpg");

                    // uploading the image
                    UploadTask uploadTask = childRef.putFile(filePath);

                    uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                        @Override
                        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                            pd.dismiss();
                            Toast.makeText(MainActivity.this, "Upload successful", Toast.LENGTH_SHORT).show();
                        }
                    }).addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            pd.dismiss();
                            Toast.makeText(MainActivity.this, "Upload Failed -> " + e, Toast.LENGTH_SHORT).show();
                        }
                    });
                }
                else {
                    Toast.makeText(MainActivity.this, "Select an image", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == PICK_IMAGE_REQUEST && 
            resultCode == RESULT_OK && data != null && 
            data.getData() != null) {
            filePath = data.getData();

            try {
                //getting image from gallery
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath);

                //Setting image to ImageView
                ivImage.setImageBitmap(bitmap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Result

android_firebase_storage_gallery.png
comments powered by Disqus