A Step-by-Step tutorial to integrate Firebase Realtime Database and User Authentication into your Android app with Kotlin and Jetpack Compose
This is part of the Firebase tutorial series:
Part 2 - Integrate Firebase Realtime Database and User Authentication into your Android App
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
Originally published at https://vtsen.hashnode.dev.
Top comments (0)