Chapters

Hide chapters

Saving Data on Android

Second Edition · Android 11 · Kotlin 1.5 · Android Studio 4.2

Using Firebase

Section 3: 11 chapters
Show chapters Hide chapters

14. Reading to & Writing from Realtime Database
Written by Fuad Kamal

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the last chapter, you integrated Realtime Database into your app. You added Firebase SDK to your app and connected the app with the Firebase project. You also learned about database rules and you set them up to allow only authenticated users access to the database. You even wrote your first data to the database which was just a sneak peek of what you’ll do in this chapter.

This chapter will teach you how to work with Realtime Database data. You’ll learn how to read and write data as well as how to do basic manipulation with that data. First, you’ll learn about performing CRUD operations on to the Realtime Database. CRUD is just an acronym for the four basic types of SQL commands: Create, Read, Update and Delete. You’ll combine all these concepts together in order to build a fully functional app with Realtime Database as the backend.

Setting up Firebase

You need to set up Firebase in order to follow along. Do the following steps:

  1. Create a project in the Firebase console.
  2. Enable Google sign-in.
  3. Set security rules to the test mode to allow everyone read and write access.
  4. Add google-service.json to both starter and final projects.

To see how to do this, go back to Chapter 12: “Firebase Overview” and Chapter 13: “Introduction to Firebase Realtime Database.”

Reading and writing data

Open the starter project, and build and run your app. If this is the first time you’re running this app you’ll need to sign in first in order to use it. To sign in, tap Sign in with Google and follow the steps on the screen. Next, click on the floating action button. A new screen opens where you can write your post. Write something and click POST.

Adding a Post.
Efyuhh e Sahp.

Saving data to the database

Open RealtimeDatabaseManager.kt. Add the following line to the class:

private val database = FirebaseDatabase.getInstance()
private fun createPost(key: String, content: String): Post {
    val user = authenticationManager.getCurrentUser()
    val timestamp = getCurrentTime()
    return Post(key, content, user, timestamp)
}
private const val POSTS_REFERENCE = "posts"
 fun addPost(content: String, onSuccessAction: () -> Unit, onFailureAction: () -> Unit) {
    //1
    val postsReference = database.getReference(POSTS_REFERENCE)
    //2
    val key = postsReference.push().key ?: ""
    val post = createPost(key, content)

    //3
    postsReference.child(key)
      .setValue(post)
      .addOnSuccessListener { onSuccessAction() }
      .addOnFailureListener { onFailureAction() }
  }
val postMessage = postText.text.toString().trim()
    if (postMessage.isNotEmpty()) {
      realtimeDatabaseManager.addPost(postMessage,
        { showToast(getString(R.string.posted_successfully)) },
        { showToast(getString(R.string.posting_failed)) } )
      finish()
    } else {
      showToast(getString(R.string.empty_post_message))
    }
Firebase Console preview - Post is saved in the database.
Zejusugo Migvaru wsakeeh - Kifw uf dituy eg nvi wayipafa.

Fetching data from the database

When it comes to reading the data from the database you have two options. You can read the data once or you can be notified whenever data changes. Since you want to see every new post from other users instantly, you’ll implement the second option.

private val postsValues = MutableLiveData<List<Post>>()
private lateinit var postsValueEventListener: ValueEventListener
private fun listenForPostsValueChanges() {
    //1
    postsValueEventListener = object : ValueEventListener {
      //2
      override fun onCancelled(databaseError: DatabaseError) {
        /* No op */
      }

      //3
      override fun onDataChange(dataSnapshot: DataSnapshot) {
        //4
        if (dataSnapshot.exists()) {
          val posts = dataSnapshot.children.mapNotNull { it.getValue(Post::class.java) }.toList()
          postsValues.postValue(posts)
        } else {
          //5
          postsValues.postValue(emptyList())
        }
      }
    }

    //6
    database.getReference(POSTS_REFERENCE)
        .addValueEventListener(postsValueEventListener)
}
fun onPostsValuesChange(): LiveData<List<Post>> {
    listenForPostsValueChanges()
    return postsValues
}
fun removePostsValuesChangesListener() {
    database.getReference(POSTS_REFERENCE).removeEventListener(postsValueEventListener)
}
private fun onPostsUpdate(posts: List<Post>) {
    feedAdapter.onFeedUpdate(posts)
}
realtimeDatabaseManager.onPostsValuesChange()
  .observe(this, Observer(::onPostsUpdate))
override fun onStop() {
    super.onStop()
    realtimeDatabaseManager.removePostsValuesChangesListener()
}
The user can preview the post added to the database.
Hba ipel rul gcigeaf gju mifj akxoj vu rsa leqidowe.

Updating and deleting data

Tap on a post to open another screen that shows post details.

The Post Details Screen.
Vwe Xepz Waluerp Vzgoiz.

Updating

Updating data in Realtime Database is almost the same as writing. You use the same method for updating - setValue() . Above the RealtimeDatabaseManager class declaration, add next constant:

private const val POST_CONTENT_PATH = "content"
fun updatePostContent(key: String, content: String) {
    //1
    database.getReference(POSTS_REFERENCE)
        //2
        .child(key)
        //3
        .child(POST_CONTENT_PATH)
        //4
        .setValue(content)
}
realtimeDatabaseManager.updatePostContent(post.id, postText.text.toString().trim())
finish()

Deleting

Deleting data in Realtime Database is very simple. You have two options. You can delete data by using setValue() and specify null as an argument or you can use removeValue() which will set the value at the specified location to null. You’ll use the latter approach.

fun deletePost(key: String) {
    database.getReference(POSTS_REFERENCE)
        .child(key)
        .removeValue()
}
realtimeDatabaseManager.deletePost(post.id)
finish()

Querying and filtering data

To show how to query data you’ll add another feature to the app. You’ll enable users to add comments to the post.

private const val COMMENTS_REFERENCE = "comments"
private const val COMMENT_POST_ID_PATH = "postId"
private val commentsValues = MutableLiveData<List<Comment>>()
private lateinit var commentsValueEventListener: ValueEventListener
private fun createComment(postId: String, content: String): Comment {
    val user = authenticationManager.getCurrentUser()
    val timestamp = getCurrentTime()
    return Comment(postId, user, timestamp, content)
}
fun addComment(postId: String, content: String) {
    val commentsReference = database.getReference(COMMENTS_REFERENCE)
    val key = commentsReference.push().key ?: ""
    val comment = createComment(postId, content)

    commentsReference.child(key).setValue(comment)
}
val comment = commentEditText.text.toString().trim()
  if (comment.isNotEmpty()) {
    realtimeDatabaseManager.addComment(post.id, comment)
    commentEditText.text.clear()
  } else {
    showToast(getString(R.string.empty_comment_message))
}
A comment added successfully.
O qojziwn anhet woglixxjojft.

The Comment is visible in the Firebase console.
Mru Heqgupm ad zodomze ih svu Xezusoqa porjene.

private fun listenForPostCommentsValueChanges(postId: String) {
    commentsValueEventListener = object : ValueEventListener {
      override fun onCancelled(databaseError: DatabaseError) {
        /* No op */
      }

      override fun onDataChange(dataSnapshot: DataSnapshot) {
        if (dataSnapshot.exists()) {
          val comments = dataSnapshot.children.mapNotNull { it.getValue(Comment::class.java) }.toList()
          commentsValues.postValue(comments)
        } else {
          commentsValues.postValue(emptyList())
        }
      }
    }

    database.getReference(COMMENTS_REFERENCE)
    	  //1
        .orderByChild(COMMENT_POST_ID_PATH)
        //2
        .equalTo(postId)
        .addValueEventListener(commentsValueEventListener)
}
private fun deletePostComments(postId: String) {
    database.getReference(COMMENTS_REFERENCE)
        .orderByChild(COMMENT_POST_ID_PATH)
        .equalTo(postId)
        .addListenerForSingleValueEvent(object : ValueEventListener {
          override fun onCancelled(databaseError: DatabaseError) {
            /* No op */
          }

          override fun onDataChange(dataSnapshot: DataSnapshot) {
            dataSnapshot.children.forEach { it.ref.removeValue() }
          }
        })
}
deletePostComments(key)
fun onCommentsValuesChange(postId: String): LiveData<List<Comment>> {
    listenForPostCommentsValueChanges(postId)
    return commentsValues
}
realtimeDatabaseManager.onCommentsValuesChange(post.id)
  .observe(this, Observer(::onCommentsUpdate))
fun removeCommentsValuesChangesListener() {
    database.getReference(COMMENTS_REFERENCE).removeEventListener(commentsValueEventListener)
}
override fun onStop() {
    super.onStop()
    realtimeDatabaseManager.removeCommentsValuesChangesListener()
}
The comment is visible on the Post Details Screen.
Hka vissorv ak vixanwa uw sfe Bump Reguumx Tkqaer.

Other features

Transactions

Realtime Database also allows you to write data to the database using a transaction. A database transaction is a unit of work that is independently executed and it must be atomic, consistent, isolated and durable. If the WhatsUp app had a feature to allow you to “like” a post you could use transactions to keep track of how many likes a given post had. Since there is a use case where multiple users could “like” the post at the same time, the transaction would allow you to always have fresh and correct data about likes.

Listening for child events

Previously you saw how to use value event listener. Often you’ll also need to know about changes in children of a specific node. In that case, you’ll need to use a child event listener. Child event listeners notify the app when child nodes are added, deleted or moved within a parent node. To add a child event listener you’ll need to call addChildEventListener() on a database reference instance, and there are four methods that you’ll need to implement. Check the official documentation https://firebase.google.com/docs/database/android/lists-of-data#child-events to learn more about child events.

Indexing

There can be a performance issue if your app frequently queries the database. To improve query performance you should consider defining indexing rules. A database index is a data structure that is used to quickly locate and access the data in a database.

Key points

  • The FirebaseDatabase object is the main entry point to the database
  • DatabaseReference represents a particular location in the database and it is used to refer to the location in the database to which you want to write to or read from.
  • push() is used to create an empty node with an auto-generated key.
  • Firebase Realtime Database has several types of listeners, and each listener type has a different kind of callback.
  • ValueEventListener listens for data changes to a specific database reference.
  • ChildEventListener listens for changes to the children of a specific database reference.
  • You need to decide how to handle listeners when the user is not actively interacting with the app. In most cases, you want to stop listening for updates. To do that you need to remove the listener.
  • For updating data in Realtime Database, use setValue() .
  • You can delete data by using setValue() and specify null as an argument or you can use removeValue() which will set the value at the specified location to null.
  • A query is a request for data or information from a database. The Query class is used for reading data and it has many useful methods that allow you to fetch the data in a way you want.
  • A database transaction is a unit of work that is independently executed and it must be atomic, consistent, isolated and durable.
  • To improve query performance you should consider defining indexing rules.

Where to go from here?

You covered a lot in this chapter. You have seen how to write data to the Realtime Database, how to listen for changes in the database and how to update and delete data. It takes a little bit of practice to get used to working with Realtime Database so feel free to play a bit with the current app. To see specifics about each method, what it does and how it does it, you can visit the official Firebase documentation to find out.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now