Home Android & Kotlin Books Saving Data on Android

13
Reading to & Writing from Realtime Database Written by Dean Djermanović

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the last chapter, you integrated Realtime Database into your app. You added Firebase SDK to your app and connected the app with Firebase project. You also learned about database rules and you set them up to allow only authenticated users the 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, 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 11: “Firebase Overview” and Chapter 12: “Introduction to Firebase Realtime Database.”

Reading and writing data

Open 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 the Sign in with Google button 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 on the Post button:

Saving data to the database

Open the RealtimeDatabaseManager class. Add the database field to the class:

private val database = FirebaseDatabase.getInstance()
private const val POSTS_REFERENCE = "posts"
private fun createPost(key: String, content: String): Post {
    val user = authenticationManager.getCurrentUser()
    val timestamp = getCurrentTime()
    return Post(key, content, user, timestamp)
}
 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))
    }

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()
}

Updating and deleting data

Tap on a post on the home screen to open another screen which shows post details:

Updating

Updating data in Realtime Database is almost the same as writing. You use the same setValue() method for updating. Add POST_CONTENT_PATH constant above RealtimeDatabaseManager class declaration:

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 method and specify null as an argument or you can use removeValue() method which will set the value at the specified location to null. You’ll use the latter approach. Open RealtimeDatabaseManager class and add the deletePost function:

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))
}

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() }
          }
        })
}
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()
}

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 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 notifiy 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 which is used to quickly locate and access the data in a database.

Key points

  • FirebaseDatabase object is the main entry point to the database
  • DatabaseReference class 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() method 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, the setValue() method is used.
  • You can delete data by using the setValue method and specify null as an argument or you can use removeValue() method which will set the value at the specified location to null.
  • A query is a request for data or information from a database. 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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.