Chapters

Hide chapters

Saving Data on Android

First Edition · Android 10 · Kotlin 1.3 · AS 3.5

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Using Firebase

Section 3: 11 chapters
Show chapters Hide chapters

18. Reading Data from Cloud Firestore
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.

In the previous chapter, you learned how to write data to the Firestore, and how to update or delete data from Firestore by implementing that functionality in the WhatsUp app. You also became familiar with the Firebase console and learned how to use it for managing data.

In this chapter, you’ll continue working on your app. Since now you still don’t have any data on the home screen when you run the app, you’ll focus on implementing reading logic. In the process, you’ll learn how to read data from the Firestore, how to listen for updates in real-time, and how queries work.

Setting up Firebase

If you skipped previous chapters, you need to setup 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.”

Be sure to use the starter project from this chapter, by opening the reading-data-from-cloud-firestore folder and its starter project from the projects folder, rather than continuing with the final project you previously worked on. It has a few things added to it, including placeholders for the code to add in this chapter.

Reading data

Like the Realtime Database, Firestore allows to read data once, or to listen for data changes in real-time.

database.collection("posts")
    .get()
    .addOnSuccessListener { result ->
      ...
    }
    .addOnFailureListener { exception ->
      ...
    }

Listening for data changes

If you don’t have the project open by now, make sure to open it, and head over to CloudFirestoreManager.kt. Add a postsRegistration field like this:

private lateinit var postsRegistration: ListenerRegistration
private fun listenForPostsValueChanges() {
  // 1
  postsRegistration = database.collection(POSTS_COLLECTION) // 2
    // 3
    .addSnapshotListener(EventListener<QuerySnapshot> { value, error ->
      // 4
      if (error != null || value == null) {
        return@EventListener
      }

      // 5
      if (value.isEmpty) {
        // 6
        postsValues.postValue(emptyList())
      } else {
        // 7
        val posts = ArrayList<Post>()
        // 8
        for (doc in value) {
          // 9
          val post = doc.toObject(Post::class.java)
          posts.add(post)
        }
        // 10
        postsValues.postValue(posts)
      }
    })
}
private fun listenForPostsUpdates() {
  cloudFirestoreManager.onPostsValuesChange()
      .observe(this, Observer(::onPostsUpdate))
}
fun stopListeningForPostChanges() = postsRegistration.remove()
override fun onStop() {
    super.onStop()
    cloudFirestoreManager.stopListeningForPostChanges()
}

Performing queries

Sometimes, you don’t want to read just the documents of certain collections. Sometimes you need to filter out, match by values, or simply skip a certain amount of documents. To do this, you use database queries. Since you don’t have any nested documents in the posts collection, let’s add something to make it a bit more complex.

Adding comments

There’s one more feature that you have in the Realtime Database version of the WhatsUp app that’s you didn’t add, and that is the ability to add a comment to the post. You’ll add that now and you’ll use that feature to see how the queries are performed.

// 1
val commentReference = database.collection(COMMENTS_COLLECTION).document()

// 2
val comment = HashMap<String, Any>()

// 3
comment[AUTHOR_KEY] = authenticationManager.getCurrentUser()
comment[CONTENT_KEY] = content
comment[POST_ID] = postId
comment[TIMESTAMP_KEY] = getCurrentTime()

// 4
commentReference
    .set(comment) // 5
    .addOnSuccessListener { onSuccessAction() } // 6
    .addOnFailureListener { onFailureAction() } // 7
...
addCommentButton.setOnClickListener {
  val comment = commentEditText.text.toString().trim()
  if (comment.isNotEmpty()) {
    cloudFirestoreManager.addComment(
      post.id,
      comment, 
      ::onCommentSuccessfullyAdded, 
      ::onCommentAddFailed
      )
  } else {
    showToast(getString(R.string.empty_comment_message))
  }
}

Listening for comments

You can add comments to the database now, but you still can’t read them. Since comments are stored in a separate collection from posts, to read them you’ll need to write a query that returns comments for the specific post. Every comment document has a post_id property that indicates to which post the comment belongs to.

private lateinit var commentsRegistration: ListenerRegistration
// 1
commentsRegistration = database.collection(COMMENTS_COLLECTION)
    // 2
    .whereEqualTo(POST_ID, postId) // 3
    // 4
    .addSnapshotListener(EventListener<QuerySnapshot> { value, error ->
      if (error != null || value == null) {
        return@EventListener
      }

      if (value.isEmpty) {
        postsValues.postValue(emptyList())
      } else {
        val comments = ArrayList<Comment>()
        for (doc in value) {
          val comment = doc.toObject(Comment::class.java)
          comments.add(comment)
        }
        commentsValues.postValue(comments)
      }
    })
cloudFirestoreManager.onCommentsValuesChange(post.id)
    .observe(this, Observer(::onCommentsUpdate))
fun stopListeningForCommentsChanges() = commentsRegistration.remove()
override fun onStop() {
    super.onStop()
    cloudFirestoreManager.stopListeningForCommentsChanges()
}

Deleting comments

One last thing that you need to add is the ability to delete the comments. You’ll delete the comments for the particular posts when that post is deleted.

// 1
database.collection(COMMENTS_COLLECTION)
    .whereEqualTo(POST_ID, postId)
    //2
    .get()
    //3
    .continueWith { task -> task.result?.documents?.forEach { it.reference.delete() } }
fun deletePost(key: String, onSuccessAction: () -> Unit, onFailureAction: () -> Unit) {
    ...
    deletePostComments(key)
}

Working offline

Like Realtime Database, Firestore can also work offline. Cloud Firestore stores a copy of data that your app is using, locally, so that you can have access to the data if the device goes offline. You can perform operations like reading, writing and querying on the local copy. When your device goes back online, Firestore automatically syncs the data with the data that is stored remotely!

Other features

Cloud Firestore has many other features. You’ll go through some of them next.

Ordering and limiting

You’ve already seen how you can specify which documents you want to fetch from the collection by using whereEqualTo(). But there’s much more you can do, on top of the whereEqualTo():

Pagination

You can have a lot of data stored in your database, but you probably don’t need all of the data all the time. Pagination allows you to split your database data into chunks so that you don’t need to fetch all of it at once.

Indexing

To ensure good performance for every query, Firestore requires an index. Firestore automatically creates indices for the basic queries for you.

Key points

Where to go from here?

You covered a lot in this chapter. You learned how to read data from Firestore and listen for data changes in real-time. You also learned what queries are and how to use them only to fetch specific documents from a collection.

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 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 Kodeco Personal Plan.

Unlock now