Skip to main content
Android Development with Kotlin
CHAPTER 24 Beginner

Firebase Cloud Firestore

Updated: May 16, 2026
30 min read

# CHAPTER 24

Firebase Cloud Firestore

1. Introduction

In Chapter 21, we utilized Room to save data locally on the device's hard drive. However, if a user uninstalls the app or buys a new phone, that data is permanently destroyed. To build social networks, messaging apps, or cloud-backed profiles, data must be stored on a remote server. Cloud Firestore is Firebase's flagship, globally scaled, NoSQL database. It allows you to store, sync, and query data for your app at a massive scale, seamlessly pushing real-time updates across all connected devices within milliseconds. In this chapter, we will master Firebase Cloud Firestore. We will break away from strict SQL tables, explore Document-based NoSQL architecture, and execute robust cloud CRUD (Create, Read, Update, Delete) operations.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Contrast Relational SQL databases (Room) with Document NoSQL databases (Firestore).
  • Configure Firestore dependencies within the Android project.
  • Write (Create/Update) Document data to specific Collections.
  • Read (Fetch) Documents utilizing asynchronous Tasks.
  • Implement Real-Time Snapshot Listeners to automatically update the UI upon cloud changes.
  • Secure data utilizing Firebase Security Rules.

3. Understanding NoSQL Architecture

Unlike Room (SQLite), Firestore does not use Rows and Columns. It is a NoSQL Database. It uses Collections and Documents.
  • Collection: A folder (e.g., users, messages, posts).
  • Document: A specific file inside that folder containing data in a JSON-like format (e.g., user_john_doe).

*Path Example:* users (Collection) -> user_id_123 (Document) -> { name: "John", age: 30 } (Data) A Document can even contain *more* Collections (Subcollections), allowing for deep, hierarchical data structuring.

4. Setup and Configuration

  1. 1. Go to the Firebase Console -> Firestore Database -> Click Create Database.
  1. 2. Start in Test Mode (This allows reading/writing without security rules for 30 days while developing).
  1. 3. Choose a location close to you.
  1. 4. In your Android Studio build.gradle.kts (Module :app), add the dependency:
kotlin
1
implementation("com.google.firebase:firebase-firestore")

5. Writing Data (Create / Update)

Let's save a user's profile to the cloud immediately after they register. We will use a Kotlin HashMap to represent the JSON data.
kotlin
123456789101112131415161718192021222324
import android.util.Log
import com.google.firebase.firestore.FirebaseFirestore

val db = FirebaseFirestore.getInstance()

fun saveUserProfile(userId: String, name: String, age: Int) {
    // 1. Package the data into a Map
    val userMap = hashMapOf(
        "fullName" to name,
        "age" to age,
        "premiumMember" to false
    )

    // 2. Target the Collection ("users") and the specific Document (the userId)
    // .set() creates the document if it doesn't exist, or OVERWRITES it if it does!
    db.collection("users").document(userId)
        .set(userMap)
        .addOnSuccessListener {
            Log.d("Firestore", "Document successfully written!")
        }
        .addOnFailureListener { e ->
            Log.w("Firestore", "Error writing document", e)
        }
}

6. Reading Data (Fetch Once)

If the user opens their profile page, we need to download their data from Firestore.
kotlin
123456789101112131415161718
fun fetchUserProfile(userId: String) {
    db.collection("users").document(userId)
        .get() // .get() fetches the data exactly ONCE
        .addOnSuccessListener { document ->
            if (document != null && document.exists()) {
                // Extract the data using the Keys we defined!
                val name = document.getString("fullName")
                val age = document.getLong("age") // Firestore stores ints as Longs!
                
                println("User Found: $name, Age: $age")
            } else {
                println("No such document!")
            }
        }
        .addOnFailureListener { exception ->
            println("Fetch failed: $exception")
        }
}

7. Real-Time Snapshot Listeners (The Magic)

Fetching data once is fine for a profile. But what if you are building a Chat App? You don't want the user to have to click "Refresh" to see new messages. Firestore offers Real-Time Listeners. You attach a listener to a Collection. If *anyone* in the world adds a new message to that Collection, Firestore instantly pushes the new data directly into your Android app in milliseconds!
kotlin
123456789101112131415
fun listenForNewMessages() {
    // Attach listener to the "messages" collection
    db.collection("messages")
        .addSnapshotListener { snapshots, e ->
            if (e != null) {
                Log.w("Firestore", "Listen failed.", e)
                return@addSnapshotListener
            }

            // This block executes IMMEDIATELY when any data in the collection changes!
            for (doc in snapshots!!.documentChanges) {
                println("New Message Arrived: ${doc.document.getString("text")}")
            }
        }
}

8. Custom Kotlin Objects (POJOs)

Manually typing document.getString("name") is tedious. Firestore can automatically convert Documents directly into Kotlin Data Classes!
kotlin
123456789101112
// Must have default values for Firestore mapping to work!
data class UserProfile(
    val fullName: String = "",
    val age: Int = 0,
    val premiumMember: Boolean = false
)

// Fetching directly into the Object:
db.collection("users").document(userId).get().addOnSuccessListener { doc ->
    val profile = doc.toObject(UserProfile::class.java)
    println(profile?.fullName) // Boom! Automatic parsing!
}

9. Common Mistakes

  • Security Rules in Production: "Test Mode" allows anyone with your project ID to delete your entire database. Before launching to the Google Play Store, you MUST write Firestore Security Rules to lock down the database (e.g., allow read, write: if request.auth != null;).
  • Ignoring Indexes: If you write a complex query like db.collection("users").whereEqualTo("age", 25).orderBy("name"), Firestore will crash your app with an error containing a URL. You must click that URL to automatically build an Index in the Firebase Console, otherwise, the query cannot execute.

10. Best Practices

  • Cost Optimization: Firestore charges money per *Document Read*. If you have a collection of 10,000 messages, and you accidentally query the entire collection every time the screen rotates, you will rack up a massive bill. Always use .limit(20) to paginate data, and rely on local caching where possible.

11. Exercises

  1. 1. Setup Firestore in the Console. Write a function that creates a new document in a products collection with name and price fields.
  1. 2. Verify the document appeared instantly in the web-based Firebase Console.

12. Coding Challenges

Challenge: Build a Global Counter. Create a document named global_stats in a stats collection. Give it a field count. In your app, create a button. Every time the button is clicked, fetch the current count, add 1, and update Firestore. Attach a SnapshotListener to the TextView so that if you run the app on two different emulators, clicking the button on Phone A instantly updates the number on Phone B!

13. MCQ Quiz with Answers

Question 1

In the context of NoSQL architecture versus Relational SQL architecture, how does Firebase Cloud Firestore strictly organize data?

Question 2

What is the explicit architectural advantage of utilizing addSnapshotListener over a standard .get() execution when architecting a messaging module?

14. Interview Questions

  • Q: Explain the structural mechanics of mapping a Firestore Document directly to a custom Kotlin Data Class via .toObject(). Why is it imperative that the Data Class constructor contains default values?
  • Q: Contrast the operational definitions of .set(), .add(), and .update() within the Firestore API. Under what conditions would an .update() call purposefully fail?
  • Q: Detail the financial implications of poor NoSQL query design. How does structuring data hierarchically (Subcollections) mitigate runaway "Document Read" billing issues?

15. FAQs

Q: Does Firestore work if the device loses internet connection? A: Yes! By default, Firestore caches all queried data locally. If the user goes offline, .get() calls will seamlessly return data from the local cache. Any .set() or .update() calls made offline will be queued locally and automatically pushed to the server the millisecond the device regains connectivity!

16. Summary

In Chapter 24, we elevated our application from a localized utility to a globally synchronized platform utilizing Firebase Cloud Firestore. We transitioned to a flexible NoSQL paradigm, orchestrating data via hierarchical Collections and Documents. We engineered robust cloud CRUD operations, seamlessly mapping JSON payloads directly to Kotlin Data Classes. Crucially, we implemented Real-Time Snapshot Listeners, establishing persistent WebSocket connections that enable instantaneous, cross-device data synchronization—the foundational architecture required for modern, dynamic applications.

17. Next Chapter Recommendation

Our app can download text, numbers, and user profiles perfectly. But modern apps require rich media. Downloading and displaying high-resolution images asynchronously without running out of RAM is incredibly difficult. Proceed to Chapter 25: Image Handling with Glide/Coil to master asynchronous image loading.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·