Bird
Raised Fist0
Android Kotlinmobile~20 mins

Cloud Firestore integration in Android Kotlin - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Simple Firestore Notes
This screen lets users add notes to Cloud Firestore and shows a list of saved notes.
Target UI
-------------------------
| Simple Firestore Notes |
-------------------------
| [Enter note here...]  |
| [Add Note Button]     |
|-----------------------|
| Notes:                |
| - Note 1              |
| - Note 2              |
| - Note 3              |
-------------------------
A TextField for user to enter a note
A Button labeled 'Add Note' to save the note to Firestore
A list below showing all saved notes from Firestore in real-time
Use Firestore collection named 'notes' with a 'text' field
Handle empty input by disabling the Add button
Starter Code
Android Kotlin
package com.example.firestoreapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.firebase.firestore.FirebaseFirestore

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            FirestoreNotesScreen()
        }
    }
}

@Composable
fun FirestoreNotesScreen() {
    // TODO: Add Firestore integration and UI here
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Task 7
Solution
Android Kotlin
package com.example.firestoreapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ListenerRegistration

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            FirestoreNotesScreen()
        }
    }
}

@Composable
fun FirestoreNotesScreen() {
    val db = remember { FirebaseFirestore.getInstance() }
    var noteText by remember { mutableStateOf("") }
    var notes by remember { mutableStateOf(listOf<String>()) }
    var listenerRegistration by remember { mutableStateOf<ListenerRegistration?>(null) }

    DisposableEffect(Unit) {
        listenerRegistration = db.collection("notes")
            .addSnapshotListener { snapshot, error ->
                if (error != null) {
                    // Handle error if needed
                    return@addSnapshotListener
                }
                if (snapshot != null) {
                    val noteList = snapshot.documents.mapNotNull { it.getString("text") }
                    notes = noteList
                }
            }
        onDispose {
            listenerRegistration?.remove()
        }
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = noteText,
            onValueChange = { noteText = it },
            label = { Text("Enter note here...") },
            modifier = Modifier.fillMaxWidth()
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = {
                if (noteText.isNotBlank()) {
                    val noteData = hashMapOf("text" to noteText.trim())
                    db.collection("notes").add(noteData)
                    noteText = ""
                }
            },
            enabled = noteText.isNotBlank(),
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Add Note")
        }
        Spacer(modifier = Modifier.height(16.dp))
        Text("Notes:", style = MaterialTheme.typography.titleMedium)
        Spacer(modifier = Modifier.height(8.dp))
        LazyColumn {
            items(notes) { note ->
                Text("- $note", style = MaterialTheme.typography.bodyLarge)
                Spacer(modifier = Modifier.height(4.dp))
            }
        }
    }
}

This solution uses Jetpack Compose to build the UI and Firebase Firestore to store and retrieve notes.

We keep the note input text in a mutableStateOf variable to update the UI as the user types.

We listen to the Firestore 'notes' collection with addSnapshotListener to get real-time updates and update the notes list state.

The Add Note button is disabled when the input is empty to prevent empty notes.

When the button is clicked, the note is trimmed and added to Firestore, then the input is cleared.

The notes list is displayed below with a simple bullet style.

Final Result
Completed Screen
-------------------------
| Simple Firestore Notes |
-------------------------
| [Enter note here...]  |
| [Add Note Button]     |
|-----------------------|
| Notes:                |
| - Buy groceries       |
| - Call mom            |
| - Read a book         |
-------------------------
User types a note in the text field
Add Note button enables only when text is not empty
Tapping Add Note saves the note to Firestore and clears input
Notes list updates instantly showing all saved notes
Stretch Goal
Add a swipe-to-delete gesture to remove notes from Firestore
💡 Hint
Use Compose's Modifier.pointerInput with detectSwipeGestures and call Firestore delete on the note document

Practice

(1/5)
1. What is the primary purpose of Cloud Firestore in an Android app?
easy
A. To compile Kotlin code
B. To design the app's user interface
C. To store and sync app data in the cloud
D. To manage app permissions

Solution

  1. Step 1: Understand Cloud Firestore's role

    Cloud Firestore is a cloud database service that stores and syncs app data.
  2. Step 2: Differentiate from other app functions

    UI design, code compilation, and permission management are handled by other tools, not Firestore.
  3. Final Answer:

    To store and sync app data in the cloud -> Option C
  4. Quick Check:

    Cloud Firestore = Data storage and sync [OK]
Hint: Remember Firestore is a cloud database, not UI or code tool [OK]
Common Mistakes:
  • Confusing Firestore with UI design tools
  • Thinking Firestore compiles code
  • Assuming Firestore manages app permissions
2. Which Kotlin code correctly initializes a Firestore instance?
easy
A. val db = FirebaseFirestore.getInstance()
B. val db = Firestore.getInstance()
C. val db = Firebase.getFirestore()
D. val db = FirestoreClient.get()

Solution

  1. Step 1: Recall Firestore initialization syntax

    The official way to get Firestore instance in Kotlin is FirebaseFirestore.getInstance().
  2. Step 2: Check other options for correctness

    Other options use incorrect class or method names not in Firestore API.
  3. Final Answer:

    val db = FirebaseFirestore.getInstance() -> Option A
  4. Quick Check:

    Correct Firestore init = val db = FirebaseFirestore.getInstance() [OK]
Hint: Use FirebaseFirestore.getInstance() to start Firestore [OK]
Common Mistakes:
  • Using Firestore.getInstance() which does not exist
  • Confusing FirebaseFirestore with Firebase class
  • Using FirestoreClient which is not part of Android SDK
3. Given this code snippet, what will be printed if the document exists with field "name" = "Alice"?
val db = FirebaseFirestore.getInstance()
db.collection("users").document("user1").get()
  .addOnSuccessListener { document ->
    if (document != null && document.exists()) {
      println(document.getString("name"))
    } else {
      println("No such document")
    }
  }
medium
A. Alice
B. No such document
C. null
D. Error at runtime

Solution

  1. Step 1: Understand the get() and success listener

    The get() fetches the document asynchronously; if it exists, the listener runs the success block.
  2. Step 2: Check document existence and print field

    Since document exists and has "name" = "Alice", println(document.getString("name")) prints "Alice".
  3. Final Answer:

    Alice -> Option A
  4. Quick Check:

    Document exists with name = Alice [OK]
Hint: If document exists, getString returns the field value [OK]
Common Mistakes:
  • Assuming document is null when it exists
  • Expecting 'No such document' incorrectly
  • Confusing getString with get() returning document
4. Identify the error in this Firestore write operation:
val db = FirebaseFirestore.getInstance()
db.collection("users").document("user1").set("name", "Bob")
medium
A. document() must be called after set()
B. Collection name cannot be "users"
C. FirebaseFirestore.getInstance() is incorrect
D. set() requires a Map or an object, not two parameters

Solution

  1. Step 1: Check set() method signature

    set() expects a single argument: a Map or an object representing fields and values.
  2. Step 2: Analyze the provided code

    Passing two parameters "name", "Bob" is invalid and causes a compile error.
  3. Final Answer:

    set() requires a Map or an object, not two parameters -> Option D
  4. Quick Check:

    set() needs one argument (Map/object) [OK]
Hint: Use a Map for set(), not separate key-value parameters [OK]
Common Mistakes:
  • Passing multiple parameters to set() instead of a Map
  • Calling document() after set() instead of before
  • Thinking collection name restrictions cause error
5. You want to listen in real-time to changes in a Firestore collection "messages" and update your UI accordingly. Which code snippet correctly sets up this listener?
hard
A. db.collection("messages").get().addOnSuccessListener { snapshots -> for (doc in snapshots) { println(doc.getString("text")) } }
B. db.collection("messages").addSnapshotListener { snapshots, e -> if (e != null) { println("Listen failed") } else { for (doc in snapshots!!) { println(doc.getString("text")) } } }
C. db.collection("messages").document().addSnapshotListener { doc, e -> println(doc?.getString("text")) }
D. db.collection("messages").addListener { snapshots -> println(snapshots) }

Solution

  1. Step 1: Understand real-time listeners in Firestore

    addSnapshotListener listens for real-time updates on a collection or document.
  2. Step 2: Analyze each option

    db.collection("messages").addSnapshotListener { snapshots, e -> if (e != null) { println("Listen failed") } else { for (doc in snapshots!!) { println(doc.getString("text")) } } } correctly uses addSnapshotListener on collection with error check and iterates snapshots. db.collection("messages").get().addOnSuccessListener { snapshots -> for (doc in snapshots) { println(doc.getString("text")) } } uses get() which is one-time fetch. db.collection("messages").document().addSnapshotListener { doc, e -> println(doc?.getString("text")) } listens on a document without specifying ID. db.collection("messages").addListener { snapshots -> println(snapshots) } uses non-existent addListener method.
  3. Final Answer:

    db.collection("messages").addSnapshotListener { snapshots, e -> if (e != null) { println("Listen failed") } else { for (doc in snapshots!!) { println(doc.getString("text")) } } } -> Option B
  4. Quick Check:

    Real-time listener = addSnapshotListener on collection [OK]
Hint: Use addSnapshotListener on collection for real-time updates [OK]
Common Mistakes:
  • Using get() instead of addSnapshotListener for real-time
  • Calling addSnapshotListener on document without ID
  • Using non-existent addListener method
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard