Integrate Firebase Realtime Database and User Authentication into your Android App

A Step-by-Step tutorial to integrate Firebase Realtime Database and User Authentication into your Android app with Kotlin and Jetpack Compose

ยท

4 min read

Integrate Firebase Realtime Database and User Authentication into your Android App

This is part of the Firebase tutorial series:

This article is the continuity of the previous article, in which we learned how to create a simple Firebase sign-in/authentication in the Android app.

So, this assumes you have already set up the Firebase console project for your app. The overview of the demo app looks like this.

1. Enable Realtime Database in Firebase Project

On the left panel of your Firebase project, click Build -> Realtime Database.

Click Create Database.

In Database options, let's use the database location from the US. Click Next.

In Security rules, let's choose test mode and click Enable.

It doesn't matter what you choose here, we're going to change the security later.

Congratulation! Now Firebase Realtime Database is enabled in your project.

2. Change Security Rules to Content-owner only Access

Go to Realtime Database -> Rules, you see something like this.

It means everyone who installs your app can read or write to your database before the expiration date. That is usually for development purposes.

What we want here is only the authenticated user can only access a certain path of the database.

So, I'm going to change the rules to the following.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth !== null && auth.uid === $uid",
        ".write": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

These rules mean only the authenticated user can access (read from and write to) the \users\<uid> path. uid is the unique string ID that is assigned to the |Firebase user.

The official document reference is here.

3. Add Firebase Database SDK to your App

build.gradle/kts (app level)

 dependencies {
     /*...*/
    implementation("com.google.firebase:firebase-database-ktx:20.1.0") 
 }

4. Write User Data to Database

The Firebase Realtime Database supports the Kotlin data class serialization and deserialization. So instead of operating on every single user data directly, you can create UserData data class below to hold them.

data class UserData (
    val name:String?,
    val age: Int?,
    val favoriteFood:List<String>?
) {
    //Note: this is needed to read the data from the firebase database
    //firebase database throws this exception: UserData does not define a no-argument constructor
    constructor() : this(null, null, null)
}

The gotcha is you MUST create a no-argument conustructor(). Firebase database throws the exception when you try to read the data from it. Damn it, it tooks me sometime to figure this out and it is not documented anywhere.

Write Data to Database

Once we signed in, we can retrieve the FirebaseUser from FirebaseAuth.getInstance().currentUser. From here, we can get the FirebaseUser.uid.

To get the reference to /users/<uid> path in the database, we use Firebase.database.reference.child("users").child(user.uid). A Firebase reference represents a particular location in your Database and can be used for reading or writing data to that Database location.

Then, we can call DatabaseReference.setValue() to write the data into the database.

val user = FirebaseAuth.getInstance().currentUser
user?.run {
    val userIdReference = Firebase.database.reference
        .child("users").child(user.uid)
    val userData = UserData(
        name = displayName,
        age = 7,
        favoriteFood = listOf("Apple", "Orange")
    )
    userIdReference.setValue(userData)
}

Before you write the data, in the Firebase console Realtime Database data tab, it looks like this, which doesn't contain any data.

After you write data into it, it becomes like this.

5. Read User Data from Database

Read Data from Database (One-time Read)

The DatabaseReference.Get() returns Task<DataSnapshot> which allows you to register a callback from the task. To read data, we register this callback task, Task.addOnSuccessListener().

val user = FirebaseAuth.getInstance().currentUser
user?.run {
    val userIdReference = Firebase.database.reference
        .child("users").child(uid)

    userIdReference.get().addOnSuccessListener { dataSnapShot ->
        val userData = dataSnapShot.getValue<UserData?>()
        //successfully read UserData from the database
    }
}

Read Data from Database (Continuous Read)

If you want to continuously reading the data instead reading on-demand, you register this ValueEventListener with DatabaseReference.addValueEventListener(). In the onDataChange() callback, you can expose the UserData to either Flow or StateFlow.

val user = FirebaseAuth.getInstance().currentUser
val userIdReference = Firebase.database.reference
    .child("users").child(user!!.uid)

userIdReference.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        val userData = dataSnapshot.getValue<UserData?>()
        //expose userData as flow or stateFlow here     
    }

    override fun onCancelled(error: DatabaseError) {
    }
})

Next Steps

The Firebase Realtime database is easy to set up (maybe not? Because it took me a while, too). The drawback of using it is not completely free, especially after you finish your quota. I will explore MangoDB next...

Source Code

GitHub Repository: Demo_FirebaseSignInRealtimeDB

Did you find this article valuable?

Support Vincent Tsen - AndroidDev Blog by becoming a sponsor. Any amount is appreciated!

ย