Firebase Database in Android with Phone Authentication - Kotlin


Configure Database:

(a) Go to https://console.firebase.google.com/





(b) Then click Add Project, a dialog will be prompted. Enter project name and choose country in it and click on Create Project.





(c) Click on Add Firebase to your Android App, another dialog will be prompted. In that, enter package name and SHA-1 (compulsary for using Database) and click Register App.


fire4.png



fire5.png

(d) Then download google_services.json file, by clicking download button and put that file in your module's root directory.


fire6.png


(e) Then navigate to Database on Side tab and click on Get Started.




Initially the Database rules will be like,


{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}



Below rule is to allow everyone to read and write data without any authentication.



{
  "rules": {
    ".read": true,
    ".write": true
  }
}



(f) Then click on Authentication in side menu and navigate to Sign-in Method. And enable Phone and Save it.

Structuring Database:

The most important task is planning your database structure, i.e., How you gonna save data and querying it. In Firebase Database, the data can only be saved as JSONObject.


{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      "contacts": { "ghopper": true },
    },
    "ghopper": { ... },
    "eclarke": { ... }
  }
}

In Case, if you would like to store it as JSONArray, it can't be stored directly. But we can store it as follows:


{
 "groups": {
  "-KeYm3ITV5PWK4u-KjVW": {
   "name": "My Family Group",
   "photo": "http://dsfds"
  },
  "-KeYmGi58ersPDWhC4EM": {
   "name": "Crime Partners",
   "photo": "http://frefg"
  }
 }
}

Here, "-KeYm3ITV5PWK4u-KjVW" and "-KeYmGi58ersPDWhC4EM" are auto-generated keys. So, the JSON Tree will read that as an array as follows:


{
 "groups": {
  [0]: {
   "name": "My Family Group",
   "photo": "http://dfdsf....",
  },
  [1]: {
   "name": "Crime Partners",
   "photo": "http://dggfdsf....",
  }
 }
}

Posting to Database:

To access Database, get reference to your exact node,


    var mFirebaseInstance = FirebaseDatabase.getInstance()
    var mFirebaseDatabase = mFirebaseInstance.getReference("users/").getRef()







Here, "users" is node here. Let's just consider it as "Table" for our understanding.

For posting data to our database as JSONObject, we can use:



     val map = mapOf("name" to nameText, "image" to imageUrl) 
     mFirebaseDatabase.child(currentUser).setValue(map) 


For posting data as Array, we can use:



     val map = mapOf("name" to nameText, "image" to imageUrl) 
     mFirebaseDatabase.push().setValue(map) 

Here, push() will auto-generate a key and post values to that. If you need that key, you can get that with push().getKey()

Querying Database:


Firebase Database can be queried either with ValueEventListener or ChildEventListener.



                 ValueEventListener will read entire data in the given path at once and notify us when any changes happened.



                  ChildEventListener will read each child in the given path and notify us when any child added, removed or changed.

Code:

Wheww!! Now we done with prerequisite and understood basic concepts. Lets proceed with code now. 

build.gradle

Include FirebaseUI in dependencies


  compile 'com.firebaseui:firebase-ui:3.1.2'

info_page.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="104dp"
        android:layout_margin="15dp"
        android:scaleType="centerCrop"
        android:src="@drawable/default_pic"
        app:layout_constraintBottom_toTopOf="@+id/textInputLayout"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />


    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        app:layout_constraintBottom_toTopOf="@+id/update"
        app:layout_constraintTop_toBottomOf="@+id/image">

        <android.support.design.widget.TextInputEditText
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Name" />
    </android.support.design.widget.TextInputLayout>

    <TextView
        android:id="@+id/update"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:padding="15dp"
        android:text="Update"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>


FirebaseActivity.kt

Using Firebase UI, we can authenticate users with phone number very easily. Once the authentication is success, we can redirect the user to next page, there he can be able to upload his name and profile picture to the Firebase Database.


class FirebaseActivity : AppCompatActivity() {

    private val RC_SIGN_IN = 1001


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val auth = FirebaseAuth.getInstance()
        if (auth.currentUser != null) {
            // If already signed in
            startActivity(Intent(this@FirebaseActivity, InfoActivity::class.java))
            finish()
        } else {
            // If not signed in
            startActivityForResult(
                    AuthUI.getInstance()
                            .createSignInIntentBuilder()
                            .setAvailableProviders(
                                    Arrays.asList(
                                            AuthUI.IdpConfig.Builder(AuthUI.PHONE_VERIFICATION_PROVIDER).build()
                                    ))
                            .build(),
                    RC_SIGN_IN)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)
            Log.v("response.toString()", "" + response.toString());
            if (resultCode == ResultCodes.OK) {
                startActivity(Intent(this@FirebaseActivity, InfoActivity::class.java))
                finish()
                return
            } else {
                when {
                    response == null -> finish()
                    response.errorCode == ErrorCodes.NO_NETWORK -> {
                        Toast.makeText(this@FirebaseActivity, "No Internet Connection", Toast.LENGTH_SHORT).show()
                        return
                    }
                    response.errorCode == ErrorCodes.UNKNOWN_ERROR -> {
                        Toast.makeText(this@FirebaseActivity, "Unknown Error", Toast.LENGTH_SHORT).show()
                        return
                    }
                }
            }
        }
    }
}

InfoActivity.kt

In this page, we have to allow the user  to upload his profile picture to Firebase Storage. Once it was uploaded, get the url of that uploaded image . Then, we have to save his name and profile picture's Url to our Firebase Database.




class InfoActivity : AppCompatActivity(), View.OnClickListener {

    val PERMISSION_REQUEST_CODE = 1001
    val PICK_IMAGE_REQUEST = 900
    lateinit var filePath: Uri
    var imageUrl: String? = ""
    lateinit var prefs: SharedPreferences
    lateinit var editor: SharedPreferences.Editor
    val FIREBASE_USERS = "users/"
    var mAuth: FirebaseAuth? = null
    var currentUser: String? = ""
    lateinit var mFirebaseInstance: FirebaseDatabase
    lateinit var mFirebaseDatabase: DatabaseReference

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.info_page)
        prefs = getSharedPreferences("", Context.MODE_PRIVATE)
        editor = prefs.edit()
        mAuth = FirebaseAuth.getInstance()
        currentUser = mAuth?.currentUser?.phoneNumber
        mFirebaseInstance = FirebaseDatabase.getInstance()
        mFirebaseDatabase = mFirebaseInstance.getReference(FIREBASE_USERS).getRef()
        
        mFirebaseDatabase.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onCancelled(data: DatabaseError?) {

            }

            override fun onDataChange(data: DataSnapshot?) {
                if (data != null)
                    if (data.hasChild(currentUser)) {
                        val map : Map<String, String>? = data.child(currentUser).getValue() as? Map<String,String>
                        Log.v("getData", "getData==" + map)
                        editor.putString("name", map?.get("name"))
                        editor.putString("image", map?.get("image"))
                        editor.putBoolean("infoAdded", true)
                        editor.commit()
                        if (prefs.getBoolean("infoAdded", false)) {
                            setUI()
                        }
                    }
            }

        })

        image.setOnClickListener(this)
        update.setOnClickListener(this)
    }

    private fun setUI() {
        name.setText(prefs.getString("name", ""))
        name.isEnabled = false
        update.text = "LogOut"
        imageUrl = prefs.getString("image", "")
        Glide.with(this@InfoActivity).load(imageUrl).into(image)
    }

    private fun chooseFile() {
        val intent = Intent().apply {
            type = "image/*"
            action = Intent.ACTION_GET_CONTENT
        }
        startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE_REQUEST)
    }

    private fun uploadFile() {
        val progress = ProgressDialog(this).apply {
            setTitle("Uploading Picture....")
            setCancelable(false)
            setCanceledOnTouchOutside(false)
            show()
        }

        val data = FirebaseStorage.getInstance()
        var value = 0.0
        var storage = data.getReference().child("mypic.jpg").putFile(filePath)
                .addOnProgressListener { taskSnapshot ->
                    value = (100.0 * taskSnapshot.bytesTransferred) / taskSnapshot.totalByteCount
                    Log.v("value", "value==" + value)
                    progress.setMessage("Uploaded.. " + value.toInt() + "%")
                }
                .addOnSuccessListener { taskSnapshot ->
                    progress.dismiss()
                    imageUrl = taskSnapshot.downloadUrl.toString()
                    Log.v("Download File", "File.." + imageUrl);

                    Glide.with(this@InfoActivity).load(imageUrl).into(image)
                }
                .addOnFailureListener { exception ->
                    exception.printStackTrace()
                }

    }

    private fun updateDetails() {
        val nameText = name.text.toString().trim()
        val map = mapOf("name" to nameText,
                "image" to imageUrl)
        mFirebaseDatabase.child(currentUser).setValue(map)

        editor.putString("name", nameText)
        editor.putString("image", imageUrl)
        editor.putBoolean("infoAdded", true)
        editor.commit()
        setUI()
    }

    private fun logOut() {
        mAuth?.signOut()
        editor.clear()
        editor.commit()
        startActivity(Intent(this, FirebaseActivity::class.java))
        finish()
    }


    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            PERMISSION_REQUEST_CODE -> {
                if (grantResults.isEmpty() || grantResults[0] == PackageManager.PERMISSION_GRANTED)
                    Toast.makeText(this@InfoActivity, "Oops! Permission Denied!!", Toast.LENGTH_SHORT).show()
                else
                    chooseFile()
            }
        }

    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != Activity.RESULT_OK) {
            return;
        }
        when (requestCode) {
            PICK_IMAGE_REQUEST -> {
                filePath = data!!.getData()
                uploadFile()

            }
        }
    }



    @RequiresApi(Build.VERSION_CODES.M)
    override fun onClick(v: View?) {
        when(v?.id){
            R.id.image -> {
                when {
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) -> {
                        if (ContextCompat.checkSelfPermission(this@InfoActivity, Manifest.permission.READ_EXTERNAL_STORAGE)
                                != PackageManager.PERMISSION_GRANTED) {
                            requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
                        } else {
                            chooseFile()
                        }
                    }

                    else -> chooseFile()
                }
            }

            R.id.update -> {
                when {
                    prefs.getBoolean("infoAdded", false) -> logOut()
                    imageUrl.toString().isEmpty() -> Toast.makeText(this@InfoActivity, "Please upload an image!", Toast.LENGTH_SHORT).show()
                    name.text.toString().trim().length == 0 -> Toast.makeText(this@InfoActivity, "Please enter your name!", Toast.LENGTH_SHORT).show()
                    else -> updateDetails()
                }
            }
        }
    }
}

Run Application:


    

Note:

* Also remember that the Phone Authentication will not work in Emulator, it requires Physical device as per Document.

* If you would like to do that in Custom UI, you can refer my other JAVA post.

Reference:

1. Website : https://firebase.google.com/docs/database/android/start/

Comments

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

RecyclerView with different number of columns using SpanSizeLookup

Using Camera in Android - Kotlin

Exploring Android Navigation Architecture Component - MVVM - Kotlin

TabLayout in Android with Kotlin

Room with LiveData, ViewModel - Android Architecture Components - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Databinding in RecyclerView - Android - Kotlin

Stripe Integration in Android - Kotlin

Using RxJava, Retrofit in Android - Kotlin