Firebase Cloud Storage helps us upload and share rich content data. Data is stored in a Google Cloud Storage bucket. With Firebase, we can perform robust operations (download/upload) regardless of network quality with strong security (Cloud Storage integrates with Firebase Authentication) and high scalability.
In this tutorial, we’re gonna look at ways to upload data from Memory, Local file, Stream in an Android App using Firebase Storage with Kotlin.
Related Posts:
– Kotlin Firebase Storage – Download Files to Memory, Local File | Android
– Kotlin Firebase Storage – Get List of Files example – Image List with FirebaseRecyclerAdapter | Android
I. How to upload file
To use the Firebase Storage to upload data, we need:
– add Firebase to Android App & enable Firebase Auth
– create a reference to the full path of the file, including the file name
– upload data using putBytes()
for in-memory data, putStream()
for stream data, putFile()
for local file.
0. Add Firebase to Android App
0.1 Add Firebase Storage
Steps to import and enable Firebase Storage is just like steps for Firebase Auth.
Just follow: Add_Firebase_to_Android_Project
0.2 Add Firebase Auth
By default, only authenticated users can upload or download data, so we need Firebase Authentication for next step.
Go to Your Firebase Project Console -> Authentication -> SIGN-IN METHOD -> Enable Email/Password.
To do without setting up Authentication, we can change the rules in the Firebase Console -> choose Project -> Storage section on the left -> Rules tab:
// change the code below service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth != null; } } } |
0.3 Check
After adding Firebase Auth & Realtime DB, we can see:
– build.gradle (project-level):
buildscript { // ... dependencies { // ... classpath 'com.google.gms:google-services:3.1.0' } } |
– build.gradle (App-level):
dependencies { // ... implementation 'com.google.firebase:firebase-auth:11.0.4' implementation 'com.google.firebase:firebase-storage:11.0.4' } apply plugin: 'com.google.gms.google-services' |
– google-services.json file:
1. Create a Reference
We cannot upload data with a reference to the root of Google Cloud Storage bucket. Reference must point to a child URL:
// Create a storage reference from our app val storageRef = FirebaseStorage.getInstance().reference // Create a reference to "javasampleapproach.jpg" val jsaRef = storageRef.child("mountains.jpg") // Create a reference to 'images/javasampleapproach.jpg' val jsaImagesRef = storageRef.child("images/javasampleapproach.jpg") |
If the file names are the same:
// true mountainsRef.name == jsaImagesRef.name // false mountainsRef.path == jsaImagesRef.path |
2. Upload Data using
2.1 putBytes()
val data: ByteArray = ... fileRef.putBytes(data) .addOnSuccessListener { taskSnapshot -> // Uri: taskSnapshot.downloadUrl // Name: taskSnapshot.metadata!!.name // Path: taskSnapshot.metadata!!.path // Size: taskSnapshot.metadata!!.sizeBytes } .addOnFailureListener { exception -> // Handle unsuccessful uploads } .addOnProgressListener { taskSnapshot -> // taskSnapshot.bytesTransferred // taskSnapshot.totalByteCount } .addOnPausedListener { taskSnapshot -> // Upload is paused } |
2.2 putFile()
val fileUri: Uri? = ... fileRef.putFile(fileUri!!) .addOnSuccessListener { taskSnapshot -> // Uri: taskSnapshot.downloadUrl // Name: taskSnapshot.metadata!!.name // Path: taskSnapshot.metadata!!.path // Size: taskSnapshot.metadata!!.sizeBytes } .addOnFailureListener { exception -> // Handle unsuccessful uploads } .addOnProgressListener { taskSnapshot -> // taskSnapshot.bytesTransferred // taskSnapshot.totalByteCount } .addOnPausedListener { taskSnapshot -> // Upload is paused } |
2.3 putStream()
val stream: InputStream = ... fileRef.putStream(stream) .addOnSuccessListener { taskSnapshot -> // Uri: taskSnapshot.downloadUrl // Name: taskSnapshot.metadata!!.name // Path: taskSnapshot.metadata!!.path // Size: taskSnapshot.metadata!!.sizeBytes } .addOnFailureListener { exception -> // Handle unsuccessful uploads } .addOnProgressListener { taskSnapshot -> // because this is a stream so: taskSnapshot.totalByteCount = -1 (always) // taskSnapshot.bytesTransferred } .addOnPausedListener { taskSnapshot -> // Upload is paused } |
3.4 Manage Uploads
We can pause, resume, or cancel upload process:
val uploadTask = fileRef.putFile(file) // putBytes() or putStream() uploadTask.pause() uploadTask.resume() uploadTask.cancel() |
II. Practice
1. Goal
We will build an Android App that can:
– create Account, sign in/sign out for Firebase Authentication.
– choose image from Gallery, then upload it to Firebase Cloud Storage using putBytes()
, putStream()
and putFile()
methods.
2. Technology
– Gradle 3.0.1
– Android Studio 3.x
– Firebase Android SDK 11.x
3. Project Structure
LoginActivity is for Authentication, then user can change to StorageActivity to upload image to Firebase Cloud Storage.
4. Step by step
4.1 Create Android Project
– Generate new Android Project with package com.javasampleapproach.kotlin.firebase.storage
.
– Follow steps to add Firebase Auth and Firebase Storage.
4.2 Enable Firebase Auth
Go to Your Firebase Project Console -> Authentication -> SIGN-IN METHOD -> Enable Email/Password.
4.3 LoginActivity
In this tutorial, we don’t explain way to authenticate an user again. To know how to implement Firebase Authentication App Client, please visit:
Kotlin Firebase Authentication – How to Sign Up, Sign In, Sign Out, Verify Email | Android
<?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:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="3"> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:gravity="center" android:text="grokonez.com" android:textSize="28sp" /> <TextView android:id="@+id/tvStatus" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:padding="4dp" android:text="Signed Out" android:textSize="14sp" /> <TextView android:id="@+id/tvDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:padding="4dp" android:textSize="14sp" tools:text="Firebase User ID: 123456789abc" /> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#E0E0E0" android:gravity="center_vertical"> <LinearLayout android:id="@+id/email_password_fields" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> <EditText android:id="@+id/edtEmail" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:hint="Email" android:inputType="textEmailAddress" /> <EditText android:id="@+id/edtPassword" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:hint="Password" android:inputType="textPassword" /> </LinearLayout> <LinearLayout android:id="@+id/email_password_buttons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/email_password_fields" android:orientation="horizontal" android:paddingLeft="16dp" android:paddingRight="16dp"> <Button android:id="@+id/btn_email_sign_in" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_weight="1" android:text="Sign In" /> <Button android:id="@+id/btn_email_create_account" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_weight="1" android:text="Create Account" /> </LinearLayout> <LinearLayout android:id="@+id/layout_signed_in_buttons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="horizontal" android:paddingLeft="16dp" android:paddingRight="16dp" android:visibility="gone" android:weightSum="2.0"> <Button android:id="@+id/btn_sign_out" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_weight="1.0" android:text="Sign Out" /> <Button android:id="@+id/btn_test_storage" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_weight="1.0" android:text="Test Storage" /> </LinearLayout> </RelativeLayout> </LinearLayout> |
package com.javasampleapproach.kotlin.firebase.storage import android.os.Bundle import android.view.View import kotlinx.android.synthetic.main.activity_login.* import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import android.widget.Toast import android.util.Log import android.text.TextUtils import android.content.Intent import android.support.v7.app.AppCompatActivity class LoginActivity : AppCompatActivity(), View.OnClickListener { private val TAG = "LoginActivity" private var mAuth: FirebaseAuth? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) btn_email_sign_in.setOnClickListener(this) btn_email_create_account.setOnClickListener(this) btn_sign_out.setOnClickListener(this) btn_test_storage.setOnClickListener(this) mAuth = FirebaseAuth.getInstance() } override fun onStart() { super.onStart() val currentUser = mAuth!!.currentUser updateUI(currentUser) } override fun onClick(view: View?) { val i = view!!.id when (i) { R.id.btn_email_create_account -> createAccount(edtEmail.text.toString(), edtPassword.text.toString()) R.id.btn_email_sign_in -> signIn(edtEmail.text.toString(), edtPassword.text.toString()) R.id.btn_sign_out -> signOut() R.id.btn_test_storage -> testStorage() } } private fun createAccount(email: String, password: String) { Log.e(TAG, "createAccount:" + email) if (!validateForm(email, password)) { return } mAuth!!.createUserWithEmailAndPassword(email, password) .addOnCompleteListener(this) { task -> if (task.isSuccessful) { Log.e(TAG, "createAccount: Success!") // update UI with the signed-in user's information val user = mAuth!!.currentUser updateUI(user) } else { Log.e(TAG, "createAccount: Fail!", task.exception) Toast.makeText(applicationContext, "Authentication failed!", Toast.LENGTH_SHORT).show() updateUI(null) } } } private fun signIn(email: String, password: String) { Log.e(TAG, "signIn:" + email) if (!validateForm(email, password)) { return } mAuth!!.signInWithEmailAndPassword(email, password) .addOnCompleteListener(this) { task -> if (task.isSuccessful) { Log.e(TAG, "signIn: Success!") // update UI with the signed-in user's information val user = mAuth!!.currentUser updateUI(user) } else { Log.e(TAG, "signIn: Fail!", task.exception) Toast.makeText(applicationContext, "Authentication failed!", Toast.LENGTH_SHORT).show() updateUI(null) } if (!task.isSuccessful) { tvStatus.text = "Authentication failed!" } } } private fun signOut() { mAuth!!.signOut() updateUI(null) } private fun validateForm(email: String, password: String): Boolean { if (TextUtils.isEmpty(email)) { Toast.makeText(applicationContext, "Enter email address!", Toast.LENGTH_SHORT).show() return false } if (TextUtils.isEmpty(password)) { Toast.makeText(applicationContext, "Enter password!", Toast.LENGTH_SHORT).show() return false } if (password.length < 6) { Toast.makeText(applicationContext, "Password too short, enter minimum 6 characters!", Toast.LENGTH_SHORT).show() return false } return true } private fun updateUI(user: FirebaseUser?) { if (user != null) { tvStatus.text = "User Email: " + user.email tvDetail.text = "Firebase User ID: " + user.uid email_password_buttons.visibility = View.GONE email_password_fields.visibility = View.GONE layout_signed_in_buttons.visibility = View.VISIBLE } else { tvStatus.text = "Signed Out" tvDetail.text = null email_password_buttons.visibility = View.VISIBLE email_password_fields.visibility = View.VISIBLE layout_signed_in_buttons.visibility = View.GONE } } private fun testStorage() { startActivity(Intent(this, StorageActivity::class.java)) } } |
4.4 StorageActivity
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="grokonez.com" android:textSize="20sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="3"> <Button android:id="@+id/btn_choose_file" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Choose File" /> <EditText android:id="@+id/edtFileName" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:hint="File Name" /> </LinearLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:text="Upload using:" android:textSize="18sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="3"> <Button android:id="@+id/btn_upload_byte" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Bytes" /> <Button android:id="@+id/btn_upload_file" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="File" /> <Button android:id="@+id/btn_upload_stream" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Stream" /> </LinearLayout> <TextView android:id="@+id/tvFileName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:text="File Name" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="5"> <ImageView android:id="@+id/imgFile" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="4.7" /> <Button android:id="@+id/btn_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Back" /> </LinearLayout> </LinearLayout> |
package com.javasampleapproach.kotlin.firebase.storage import android.content.Intent import android.graphics.Bitmap import android.net.Uri import android.provider.MediaStore import android.support.v7.app.AppCompatActivity import android.os.Bundle import kotlinx.android.synthetic.main.activity_storage.* import android.text.TextUtils import android.util.Log import android.view.View import android.webkit.MimeTypeMap import android.widget.* import com.google.firebase.storage.FirebaseStorage import com.google.firebase.storage.StorageReference import java.io.ByteArrayOutputStream import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream class StorageActivity : AppCompatActivity(), View.OnClickListener { private val TAG = "StorageActivity" //track Choosing Image Intent private val CHOOSING_IMAGE_REQUEST = 1234 private var fileUri: Uri? = null private var bitmap: Bitmap? = null private var imageReference: StorageReference? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_storage) tvFileName.text = "" imageReference = FirebaseStorage.getInstance().reference.child("images") btn_choose_file.setOnClickListener(this) btn_upload_byte.setOnClickListener(this) btn_upload_file.setOnClickListener(this) btn_upload_stream.setOnClickListener(this) btn_back.setOnClickListener(this) } override fun onClick(view: View?) { val i = view!!.id when (i) { R.id.btn_choose_file -> showChoosingFile() R.id.btn_upload_byte -> uploadBytes() R.id.btn_upload_file -> uploadFile() R.id.btn_upload_stream -> uploadStream() R.id.btn_back -> finish() } } private fun uploadBytes() { if (fileUri != null) { val fileName = edtFileName.text.toString() if (!validateInputFileName(fileName)) { return } val baos = ByteArrayOutputStream() bitmap!!.compress(Bitmap.CompressFormat.JPEG, 50, baos) val data: ByteArray = baos.toByteArray() val fileRef = imageReference!!.child(fileName + "." + getFileExtension(fileUri!!)) fileRef.putBytes(data) .addOnSuccessListener { taskSnapshot -> Log.e(TAG, "Uri: " + taskSnapshot.downloadUrl) Log.e(TAG, "Name: " + taskSnapshot.metadata!!.name) tvFileName.text = taskSnapshot.metadata!!.path + " - " + taskSnapshot.metadata!!.sizeBytes / 1024 + " KBs" Toast.makeText(this, "File Uploaded ", Toast.LENGTH_LONG).show() } .addOnFailureListener { exception -> Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show() } .addOnProgressListener { taskSnapshot -> // progress percentage val progress = 100.0 * taskSnapshot.bytesTransferred / taskSnapshot.totalByteCount // percentage in progress dialog val intProgress = progress.toInt() tvFileName.text = "Uploaded " + intProgress + "%..." } .addOnPausedListener { System.out.println("Upload is paused!") } } else { Toast.makeText(this, "No File!", Toast.LENGTH_LONG).show() } } private fun uploadFile() { if (fileUri != null) { val fileName = edtFileName.text.toString() if (!validateInputFileName(fileName)) { return } val fileRef = imageReference!!.child(fileName + "." + getFileExtension(fileUri!!)) fileRef.putFile(fileUri!!) .addOnSuccessListener { taskSnapshot -> Log.e(TAG, "Uri: " + taskSnapshot.downloadUrl) Log.e(TAG, "Name: " + taskSnapshot.metadata!!.name) tvFileName.text = taskSnapshot.metadata!!.path + " - " + taskSnapshot.metadata!!.sizeBytes / 1024 + " KBs" Toast.makeText(this, "File Uploaded ", Toast.LENGTH_LONG).show() } .addOnFailureListener { exception -> Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show() } .addOnProgressListener { taskSnapshot -> // progress percentage val progress = 100.0 * taskSnapshot.bytesTransferred / taskSnapshot.totalByteCount // percentage in progress dialog val intProgress = progress.toInt() tvFileName.text = "Uploaded " + intProgress + "%..." } .addOnPausedListener { System.out.println("Upload is paused!") } } else { Toast.makeText(this, "No File!", Toast.LENGTH_LONG).show() } } private fun uploadStream() { if (fileUri != null) { val fileName = edtFileName.text.toString() if (!validateInputFileName(fileName)) { return } try { val stream: InputStream = contentResolver.openInputStream(fileUri) val fileRef = imageReference!!.child(fileName + "." + getFileExtension(fileUri!!)) fileRef.putStream(stream) .addOnSuccessListener { taskSnapshot -> Log.e(TAG, "Uri: " + taskSnapshot.downloadUrl) Log.e(TAG, "Name: " + taskSnapshot.metadata!!.name) tvFileName.text = taskSnapshot.metadata!!.path + " - " + taskSnapshot.metadata!!.sizeBytes / 1024 + " KBs" Toast.makeText(this, "File Uploaded ", Toast.LENGTH_LONG).show() } .addOnFailureListener { exception -> Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show() } .addOnProgressListener { taskSnapshot -> // because this is a stream so: // taskSnapshot.getTotalByteCount() = -1 (always) tvFileName.text = "Uploaded " + taskSnapshot.bytesTransferred + " Bytes..." } .addOnPausedListener { System.out.println("Upload is paused!") } } catch (e: FileNotFoundException) { e.printStackTrace() } } else { Toast.makeText(this, "No File!", Toast.LENGTH_LONG).show() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (bitmap != null) { bitmap!!.recycle() } if (requestCode == CHOOSING_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.data != null) { fileUri = data.data try { bitmap = MediaStore.Images.Media.getBitmap(contentResolver, fileUri) imgFile.setImageBitmap(bitmap) } catch (e: IOException) { e.printStackTrace() } } } private fun showChoosingFile() { val intent = Intent() intent.type = "image/*" intent.action = Intent.ACTION_GET_CONTENT startActivityForResult(Intent.createChooser(intent, "Select Image"), CHOOSING_IMAGE_REQUEST) } private fun getFileExtension(uri: Uri): String { val contentResolver = contentResolver val mime = MimeTypeMap.getSingleton() return mime.getExtensionFromMimeType(contentResolver.getType(uri)) } private fun validateInputFileName(fileName: String): Boolean { if (TextUtils.isEmpty(fileName)) { Toast.makeText(this, "Enter file name!", Toast.LENGTH_SHORT).show() return false } return true } } |
4.5 Android Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javasampleapproach.kotlin.firebase.storage"> <application ...> <activity android:name=".LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".StorageActivity"></activity> </application> </manifest> |
4.6 Run & Check result
– Use Android Studio, build and Run your Android App:
– Open Firebase Project Console -> Storage:
III. Source code
Kotlin-FirebaseStorage-upload-data
Last updated on July 13, 2018.