Computer scienceProgramming languagesKotlinAdditional instrumentsDatabase and StorageFirebase development

Firebase Realtime Database

6 minutes read

The Firebase Realtime Database (RTDB) is a database provided by Firebase for storing and synchronizing data between users in real time. It's a NoSQL cloud storage database that lets you store and synchronize data across different devices without the need to write complex server code.

Main features of Firebase Realtime Database

  1. Based on JSON
    • Data in the RTDB is stored in JSON format, making the data structure quite flexible. You can easily add, modify, and delete data without needing to change the schema as in relational databases.
    • Developers can directly manipulate data in the database using standard JSON manipulation methods, simplifying interactions.
  2. Data synchronization in real time
    • One of the most attractive aspects of the RTDB is automatic data synchronization. When data changes, all connected clients receive these changes in real time.
    • This is particularly useful for apps where multiple users may interact with the same data simultaneously, such as in chats or multiplayer games.
  3. Offline data access
    • The Firebase Realtime Database provides the capability to store data locally on the device, allowing your apps to function even without internet access.
    • As soon as the device reconnects to the internet, all local changes will be automatically synchronized with the cloud, and vice versa—any changes in the cloud will be delivered to the device.

Basics of Working with Realtime Database in Kotlin

To start using the Realtime Database in your project, add dependencies to your build.gradle.kts:

dependencies {
    // Import the BoM for the Firebase platform
    implementation(platform("com.google.firebase:firebase-bom:32.2.2"))

    // Add the dependency for the Realtime Database library
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation("com.google.firebase:firebase-database-ktx")
}

To start working with a database, first get a reference to it:

val database = FirebaseDatabase.getInstance().reference

Here's an example of writing data:

val user = User("Anna", "[email protected]")
database.child("users").child(userId).setValue(user)

Here, User is a simple class with some fields.

Below is an example of reading data:

val userReference = database.child("users").child(userId)

userReference.get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

One of the advantages of the Realtime Database is the ability to listen for data changes in real time. This can be done using ValueEventListener or ChildEventListener.

Here's an example of subscribing to changes using ValueEventListener:

val userListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        val user = dataSnapshot.getValue(User::class.java)
        Log.i("firebase", "User name: ${user?.name}, email: ${user?.email}")
    }

    override fun onCancelled(databaseError: DatabaseError) {
        Log.w("firebase", "loadUser:onCancelled", databaseError.toException())
    }
}

userReference.addValueEventListener(userListener)

To unsubscribe from updates, simply remove the listener:

userReference.removeEventListener(userListener)

Security and Access Rules for Realtime Database

Security rules are a JSON structure that determines which operations are allowed for specific paths in your database. They contain keys such as .read and .write, which define read or write permissions for specific paths, respectively. Below are several examples of rules for different access types.

  • Open access (not recommended)

Here, everyone can read and write data. Do not use such rules in production!

    {
        "rules": {
            ".read": true,
            ".write": true
        }
    }
    
  • Private access

Now, no one, except authenticated users, can read or write data.

    {
        "rules": {
            ".read": "auth != null",
            ".write": "auth != null"
        }
    }
    
  • Role-based access

You might have specific roles in your database: for example, administrators and users.

   {
        "rules": {
            "adminContent": {
                ".read": "root.child('admins').child(auth.uid).exists()",
                ".write": "root.child('admins').child(auth.uid).exists()"
            },
            "userContent": {
                ".read": "auth != null",
                ".write": "auth.uid === $user_id"
            }
        }
    }
    
  • Time-limited access

In this case, users can add data but only until a certain date.

   {
        "rules": {
            "limitedTimeContent": {
                ".write": "now < 1672531200000" // for example, until January 1, 2023
            }
        }
    }
    

When working with Firebase security rules, it's crucial to test thoroughly and ensure that your data is appropriately protected. While the rules allow for flexible data access configurations, mistakes in them can lead to vulnerabilities.

Working with Complex Queries in Realtime Database in Kotlin

The Realtime Database provides several methods for sorting and filtering data.

  • Sort by key
    val reference = FirebaseDatabase.getInstance().getReference("path_to_data")
    reference.orderByKey().addListenerForSingleValueEvent(/* ... */)
  • Sort by value
    reference.orderByValue().addListenerForSingleValueEvent(/* ... */)
  • Filtering data
    If you want to get only certain items, use the startAt(), endAt(), or equalTo() methods.
reference.orderByChild("child_name")
    .startAt("value")
    .endAt("value")
    .addListenerForSingleValueEvent(/* ... */)

For pagination, you can use the limitToFirst(n) and limitToLast(n) methods:

  • Limit on the initial items
    reference.limitToFirst(10).addListenerForSingleValueEvent(/* ... */)
  • Limit on the last items
    reference.limitToLast(10).addListenerForSingleValueEvent(/* ... */)

Integration with other Firebase Components

  • Firebase Authentication for user identification

Firebase Authentication offers several methods for authenticating users, including email and password, Google, Facebook, and more. After successful authentication, you can obtain the user's unique identifier (UID), which can be used to create unique paths in the Realtime Database.

FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
    .addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val user = FirebaseAuth.getInstance().currentUser
            val uid = user?.uid
            // Use uid to work with user data in Realtime Database
        }
    }
  • Firebase Storage for media file storage

Firebase Storage provides the ability to upload and download media files. After uploading a file, you can get its URL and save it to the Realtime Database.

val storageReference = FirebaseStorage.getInstance().getReference("path/to/storage")
storageReference.putFile(uri).addOnSuccessListener { taskSnapshot ->
    val downloadUrl = taskSnapshot.storage.downloadUrl
    downloadUrl.addOnSuccessListener { uri ->
        val fileUrl = uri.toString()
        // Save the file URL in the Realtime Database
    }
}

Conclusion

The Firebase Realtime Database (RTDB) is a tool that provides developers with the ability to store and synchronize data in real time. Like any tool, RTDB has its advantages and disadvantages, and depending on your project's needs, other alternatives may be preferable. When choosing, it is important to take into account the specifics of your project and the requirements for the database.

3 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo