During the 2016 Google I/O conference, Firebase was reintroduced to the developer community as a major tool for providing quick back-end support for web pages and mobile applications. This tutorial will introduce you to the file storage and retrieval functionality available for your Android apps.
To learn more about Firebase's real-time database, analytics, crash reporting and authentication, check out some of our other tutorials here on Envato Tuts+.
Authentication Setup
For the sake of brevity, we'll skip over the general setup process for Firebase in your Android app and the Firebase console. If this is your first time trying Firebase in an app, I suggest first checking out Ashraff Hathibelagal's article Getting Started With Firebase for Android.
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.
Selecting that file in the console will bring up a detail view, allowing you to inspect files that have previously been uploaded.
Downloading Files From an Android App
Now that you've got a file stored in Firebase, 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/image" 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://tutsplus-firebase.appspot.com").child("android.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("images", "jpg"); storageRef.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { Bitmap bitmap = BitmapFactory.decodeFile(localFile.getAbsolutePath()); mImageView.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
.
Downloading Files as a Byte Array
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.
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); mImageView.setImageBitmap(bitmap); } });
Getting a File's URL
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.e("Tuts+", "uri: " + uri.toString()); //Handle whatever you're going to do with the URL here } });
The above method will print out the URL for the image we manually uploaded earlier in the Android Monitor.
E/Tuts+: uri: https://firebasestorage.googleapis.com/v0/b/tutsplus-firebase.appspot.com/o/android.jpg?alt=media&token=1828111c-78e7-4640-b418-c65e7571576a
Uploading From an Android App
Now that you know how to download files from Firebase, it's time to work on uploads. As you saw when downloading from Firebase Storage, the processes for each form of your data are fairly similar. Uploading is no different, so we'll simply dive in to how you can move files from your app into Firebase Storage.
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://tutsplus-firebase.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[]
.
mImageView.setDrawingCacheEnabled(true); mImageView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); mImageView.layout(0, 0, mImageView.getMeasuredWidth(), mImageView.getMeasuredHeight()); mImageView.buildDrawingCache(); Bitmap bitmap = Bitmap.createBitmap(mImageView.getDrawingCache()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); 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.addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { } }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() { @Override public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { } });
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://tutsplus-firebase.appspot.com").child("test.txt"); InputStream stream = getResources().openRawResource(R.raw.test); UploadTask uploadTask = storageReference.putStream(stream);
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://tutsplus-firebase.appspot.com").child("test2.txt"); File file = null; try { file = File.createTempFile("test2", "txt"); } catch( IOException e ) { } UploadTask uploadTask = storageReference.putFile(Uri.fromFile(file));
Controlling Uploads and Using Callbacks
While the files we've uploaded so far have been small, there may be times where you have larger uploads that will take a fair amount of time. Firebase provides a few methods with UploadTask
that will allow you to control the flow of an upload and listen to progress and state changes. These methods include pause()
, resume()
, and cancel()
. pause()
and resume()
will let you pause and resume an UploadTask
, whereas cancel()
will completely stop it. In addition, you can use an OnPauseListener
and OnProgressListener
to keep track of upload progress and pause states.
mPause = (Button) findViewById(R.id.pause); mResume = (Button) findViewById(R.id.resume); mPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mUploadTask.pause(); } }); mResume.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mUploadTask.resume(); } }); FirebaseStorage storage = FirebaseStorage.getInstance(); StorageReference storageReference = storage.getReferenceFromUrl("gs://tutsplus-firebase.appspot.com").child("image.jpg"); InputStream stream = getResources().openRawResource(R.raw.image); mUploadTask = storageReference.putStream(stream); mUploadTask.addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() { @Override public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { Log.e("Tuts+", "upload paused!"); } }); mUploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() { @Override public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { Log.e("Tuts+", "Bytes uploaded: " + taskSnapshot.getBytesTransferred()); } });
The above code will allow you to control the upload process for a somewhat large (1MB, in this case) image and see its states changing in the Android log when the pause and resume buttons are pressed.
E/Tuts+: Bytes uploaded: 524288 E/Tuts+: upload paused! E/Tuts+: Bytes uploaded: 786432
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 (mStorageReference != null) { outState.putString(EXTRA_STORAGE_REFERENCE_KEY, mStorageReference.toString()); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); final String stringRef = savedInstanceState.getString(EXTRA_STORAGE_REFERENCE_KEY); if (stringRef == null) { return; } mStorageReference = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef); List<FileDownloadTask> tasks = mStorageReference.getActiveDownloadTasks(); for( FileDownloadTask task : tasks ) { task.addOnSuccessListener(this, new OnSuccessListener<FileDownloadTask.TaskSnapshot>() { @Override public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { Log.e("Tuts+", "download successful!"); } }); } }
Conclusion
In this tutorial you've learned a great deal about Firebase and its available options for file storage. You should now be able to upload and download files, control data transfers, and handle changes in your app's activity lifecycle while a transaction is occurring.
While we've just scratched the surface of what you can do in Firebase, understanding this tool will hopefully enable you to expand the capabilities of your own apps and provide great experiences for your users.
And in the meantime, check out some of our other articles on Android development!
Comments