Firestore Real-time Listener — Complete Guide
In this tutorial, you'll learn about Firestore Real. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
The Problem
Your Firestore listener keeps running after the Activity is destroyed (memory leak), or you're getting duplicate events because you registered the listener twice.
Wrong Approach ❌
// Listener registered but never removed
class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
db.collection("users").addSnapshotListener { snapshot, e ->
// Update UI — but listener leaks on config change!
}
}
// No onStop — listener keeps running
}
Output: Listener accumulates on every rotation. Multiple listeners fire simultaneously.
Right Approach ✅
class MainActivity : AppCompatActivity() {
private var userListener: ListenerRegistration? = null
override fun onStart() {
super.onStart()
startListening()
}
override fun onStop() {
super.onStop()
stopListening()
}
private fun startListening() {
userListener = db.collection("users")
.document(userId)
.addSnapshotListener { snapshot, error ->
if (error != null) {
Log.w("Firestore", "Listen error", error)
return@addSnapshotListener
}
val source = if (snapshot?.metadata?.isFromCache == true) "local" else "server"
Log.d("Firestore", "Data from $source")
snapshot?.toObject(User::class.java)?.let { user ->
updateUI(user)
}
}
}
private fun stopListening() {
userListener?.remove()
userListener = null
}
// Using ViewModel + LiveData for lifecycle safety
}
// Better: use a repository with Flow
class UserRepository(private val db: FirebaseFirestore) {
fun observeUser(userId: String): Flow<User?> = callbackFlow {
val listener = db.collection("users").document(userId)
.addSnapshotListener { snapshot, error ->
if (error != null) {
cancel(error.message ?: "Unknown error", error)
return@addSnapshotListener
}
trySend(snapshot?.toObject(User::class.java))
}
awaitClose { listener.remove() }
}
}
// In ViewModel — lifecycle-safe collection
class UserViewModel(private val repo: UserRepository) : ViewModel() {
val user: StateFlow<User?> = repo.observeUser(userId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
}
Output: Listeners properly cleaned up. Lifecycle-safe data flow.
Prevention
- Always call
listenerRegistration.remove()inonStop(). - Use
callbackFlowin repositories for clean listener management. - Use
awaitClose { listener.remove() }to ensure cleanup. - Use
WhileSubscribed(5000)in ViewModel to handle config changes gracefully.
Common Mistakes with firestore realtime
- Forgetting
deriving (Show, Eq)on custom data types needed for debugging - Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
- Using
headandtailinstead of pattern matching, causing runtime errors on empty lists
These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro