We had known the way to upload/download data. In this tutorial, we’re gonna look at way to get list of files example – display list of Images with Firebase UI Database FirebaseRecyclerAdapter
.
Related Posts:
– Kotlin Firebase Storage – Upload Data from Memory, Local File, Stream | Android
– Kotlin Firebase Storage – Download Files to Memory, Local File | Android
– Kotlin Firebase Realtime Database – Read/Write Data example | Android
I. How to do
To display list of Images, we need to:
– add Firebase to Android App & enable Firebase Auth
– use Firebase Cloud Storage to upload and store images
– use Firebase Realtime Database to store images’ information (name + url)
– use FirebaseRecyclerAdapter
to display images in RecyclerView
with the help of Picasso lib.
0. Depedencies
After setup Project, build.gradle file (App-level):
dependencies { // ... implementation 'com.google.firebase:firebase-auth:11.0.4' implementation 'com.google.firebase:firebase-storage:11.0.4' implementation 'com.google.firebase:firebase-database:11.0.4' implementation 'com.firebaseui:firebase-ui-database:2.3.0' implementation 'com.squareup.picasso:picasso:2.5.2' } apply plugin: 'com.google.gms.google-services' |
1. Upload and store images
Use Firebase Cloud Storage:
// store image at "images/filename.extension" on Firebase Cloud Storage imageReference = FirebaseStorage.getInstance().reference.child("images") fileRef = imageReference!!.child(fileName + "." + getFileExtension(fileUri!!)) fileRef!!.putFile(fileUri!!) .addOnSuccessListener { taskSnapshot -> val name = taskSnapshot.metadata!!.name val url = taskSnapshot.downloadUrl.toString() // use Firebase Realtime Database to store [name + url] writeNewImageInfoToDB(name!!, url) } .addOnFailureListener { exception -> // ... } .addOnProgressListener { taskSnapshot -> // progress percentage val progress = 100.0 * taskSnapshot.bytesTransferred / taskSnapshot.totalByteCount // percentage in progress val intProgress = progress.toInt() } .addOnPausedListener { taskSnapshot -> // ... } |
2. Store images’ information
Use Firebase Realtime Database:
dataReference = FirebaseDatabase.getInstance().getReference("images") private fun writeNewImageInfoToDB(name: String, url: String) { val info = UploadInfo(name, url) val key = dataReference!!.push().key dataReference!!.child(key).setValue(info) } |
3. Display list of Images
3.1 Model and ViewHolder
– Model
class is a class that represents the data from Firebase:
class UploadInfo { var name: String = "" var url: String = "" // ... } |
– ViewHolder
layout (R.layout.item_image) with UI items that correspond to Model
fields:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:id="@+id/tvImgName" /> <ImageView android:id="@+id/imgView" /> </LinearLayout> |
– ViewHolder
class contains Android UI fields that point to layout items:
import android.support.v7.widget.RecyclerView import android.view.View class ImgViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) // viewHolder.itemView.imgView // viewHolder.itemView.tvImgName |
3.2 FirebaseRecyclerAdapter subclass
We need a subclass of the FirebaseRecyclerAdapter
and implement its populateViewHolder()
method:
private var mAdapter: FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>? = null // ... // dataReference = FirebaseDatabase.getInstance().getReference("images") val query = dataReference!!.limitToLast(3) mAdapter = object : FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>( UploadInfo::class.java, R.layout.item_image, ImgViewHolder::class.java, query) { override fun populateViewHolder(viewHolder: ImgViewHolder?, model: UploadInfo?, position: Int) { viewHolder!!.itemView.tvImgName.text = model!!.name Picasso.with(this@StorageActivity) .load(model.url) .error(R.drawable.common_google_signin_btn_icon_dark) .into(viewHolder.itemView.imgView) } } |
Now look at these lines of code:
val query = dataReference!!.limitToLast(3) mAdapter = object : FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>( UploadInfo::class.java, R.layout.item_image, ImgViewHolder::class.java, query) |
– We tell FirebaseRecyclerAdapter
object to use UploadInfo.class when reading from the database.
– Each Message will be displayed in a R.layout.item_image
(that has 2 elements: tvImgName, imgView).
– We indicate class for ViewHolder
– We can just give reference to database node or sort/filter data by using Query
:
val query = mDataReference val query = mDataReference.orderByKey() // orderByValue() or orderByChild("...") val query = mDataReference.limitToLast(8) // limitToFirst(..), startAt(...), endAt(...), equalTo(...) |
FirebaseRecyclerAdapter
will call populateViewHolder()
method for each Model it finds in database. It passes us the Model
and a ViewHolder
.
So what we should do is map the fields from model to the correct UI elements:
override fun populateViewHolder(viewHolder: ImgViewHolder?, model: UploadInfo?, position: Int) { viewHolder!!.itemView.tvImgName.text = model!!.name Picasso.with(this@StorageActivity) .load(model.url) .error(R.drawable.common_google_signin_btn_icon_dark) .into(viewHolder.itemView.imgView) } |
3.3 RecyclerView
Now we set the adapter for RecyclerView
object to provide child views on demand:
val layoutManager = LinearLayoutManager(this) layoutManager.reverseLayout = false // rcvListImg: RecyclerView item from layout file rcvListImg.setHasFixedSize(true) rcvListImg.layoutManager = layoutManager // mAdapter: FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>? rcvListImg.adapter = mAdapter |
Remember to call adapter cleanup()
method to stop listening for changes in the Firebase database:
override fun onDestroy() { super.onDestroy() mAdapter!!.cleanup() } |
II. Practice
1. Goal
We will build an Android App that can:
– create Account, sign in/sign out for Firebase Authentication.
– read/write user to Firebase Realtime Database.
(2 lines above come from this Post).
– upload image to Firebase Cloud Storage (from this Post)
– display list of Images using FirebaseUI FirebaseRecyclerAdapter
.
2. Technology
– Gradle 3.0.1
– Android Studio 3.x
– Firebase Android SDK 11.x
– Firebase UI Database 2.3.0
– Picasso 2.5.2
3. Project Structure
LoginActivity is for Authentication, then user can enter StorageActivity to upload Image to Firebase Storage, Image’s information to Firebase Database, and show list of Images.
4. Step by step
4.1 Create Android Project
– Generate new Android Project with package com.javasampleapproach.kotlin.firebase.storagerealdb
.
– Add Firebase Auth, Firebase Realtime Database, Firebase Storage, Picasso.
– Check dependency after setup Project, build.gradle file (App-level):
dependencies { implementation 'com.android.support:appcompat-v7:26.0.1' // ... implementation 'com.google.firebase:firebase-auth:11.0.4' implementation 'com.google.firebase:firebase-storage:11.0.4' implementation 'com.google.firebase:firebase-database:11.0.4' implementation 'com.firebaseui:firebase-ui-database:2.3.0' implementation 'com.squareup.picasso:picasso:2.5.2' } apply plugin: 'com.google.gms.google-services' |
4.2 Enable Firebase Auth
Go to Your Firebase Project Console -> Authentication -> SIGN-IN METHOD -> Enable Email/Password.
4.3 Model
package com.javasampleapproach.kotlin.firebase.storagerealdb.model import com.google.firebase.database.IgnoreExtraProperties @IgnoreExtraProperties class UploadInfo { var name: String = "" var url: String = "" constructor() {} constructor(name: String, url: String) { this.name = name this.url = url } } |
4.4 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.storagerealdb 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.5 ViewHolder
<?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="wrap_content" android:orientation="vertical" android:paddingBottom="7dp"> <TextView android:id="@+id/tvImgName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:text="Image Name" android:textColor="@android:color/holo_blue_dark" /> <ImageView android:id="@+id/imgView" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> |
package com.javasampleapproach.kotlin.firebase.storagerealdb.viewholder import android.support.v7.widget.RecyclerView import android.view.View class ImgViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) |
4.6 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:id="@+id/tvFileLoad" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:text="% File Load" android:visibility="gone"/> <Button android:id="@+id/btn_upload_file" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="Upload" /> <android.support.v7.widget.RecyclerView android:id="@+id/rcvListImg" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:padding="16dp" /> <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> |
package com.javasampleapproach.kotlin.firebase.storagerealdb import android.app.Activity import android.content.Intent import android.net.Uri import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.text.TextUtils import android.util.Log import android.view.View import android.webkit.MimeTypeMap import android.widget.Toast import com.firebase.ui.database.ChangeEventListener import com.google.firebase.storage.StorageReference import com.google.firebase.database.DatabaseReference import com.javasampleapproach.kotlin.firebase.storagerealdb.viewholder.ImgViewHolder import com.javasampleapproach.kotlin.firebase.storagerealdb.model.UploadInfo import com.firebase.ui.database.FirebaseRecyclerAdapter import com.google.firebase.database.DataSnapshot import com.google.firebase.database.FirebaseDatabase import com.google.firebase.storage.FirebaseStorage import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.activity_storage.* import kotlinx.android.synthetic.main.item_image.view.* 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 dataReference: DatabaseReference? = null private var imageReference: StorageReference? = null private var fileRef: StorageReference? = null private var mAdapter: FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_storage) tvFileLoad.text = "" tvFileLoad.visibility = View.GONE dataReference = FirebaseDatabase.getInstance().getReference("images") imageReference = FirebaseStorage.getInstance().reference.child("images") btn_choose_file.setOnClickListener(this) btn_upload_file.setOnClickListener(this) btn_back.setOnClickListener(this) val layoutManager = LinearLayoutManager(this) layoutManager.reverseLayout = false rcvListImg.setHasFixedSize(true) rcvListImg.layoutManager = layoutManager val query = dataReference!!.limitToLast(3) mAdapter = object : FirebaseRecyclerAdapter<UploadInfo, ImgViewHolder>( UploadInfo::class.java, R.layout.item_image, ImgViewHolder::class.java, query) { override fun populateViewHolder(viewHolder: ImgViewHolder?, model: UploadInfo?, position: Int) { viewHolder!!.itemView.tvImgName.text = model!!.name Picasso.with(this@StorageActivity) .load(model.url) .error(R.drawable.common_google_signin_btn_icon_dark) .into(viewHolder.itemView.imgView) } } rcvListImg.adapter = mAdapter } override fun onClick(view: View?) { val i = view!!.id when (i) { R.id.btn_choose_file -> showChoosingFile() R.id.btn_upload_file -> uploadFile() R.id.btn_back -> finish() } } private fun uploadFile() { if (fileUri != null) { val fileName = edtFileName.text.toString() if (!validateInputFileName(fileName)) { return } tvFileLoad.visibility = View.VISIBLE fileRef = imageReference!!.child(fileName + "." + getFileExtension(fileUri!!)) fileRef!!.putFile(fileUri!!) .addOnSuccessListener { taskSnapshot -> val name = taskSnapshot.metadata!!.name val url = taskSnapshot.downloadUrl.toString() Log.e(TAG, "Uri: " + taskSnapshot.downloadUrl) Log.e(TAG, "Name: " + taskSnapshot.metadata!!.name) tvFileLoad.text = taskSnapshot.metadata!!.path + " - " + taskSnapshot.metadata!!.sizeBytes / 1024 + " KBs" writeNewImageInfoToDB(name!!, url) 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 val intProgress = progress.toInt() tvFileLoad.text = "Uploaded " + intProgress + "%..." } .addOnPausedListener { System.out.println("Upload is paused!") } } else { Toast.makeText(this, "No File!", Toast.LENGTH_LONG).show() } } private fun writeNewImageInfoToDB(name: String, url: String) { val info = UploadInfo(name, url) val key = dataReference!!.push().key dataReference!!.child(key).setValue(info) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == CHOOSING_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.data != null) { fileUri = data.data } } override fun onDestroy() { super.onDestroy() mAdapter!!.cleanup() } 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.7 Android Manifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javasampleapproach.kotlin.firebase.storagerealdb"> <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.8 Run & Check result
– Use Android Studio, build and Run your Android App:
– Firebase Console:
+ Storage:
+ Database:
III. Source code
Kotlin-FireBaseStorageRealtimeDB
Last updated on July 13, 2018.