Skip to content

Firestore Real-time Listener — Complete Guide

DodaTech Updated 2026-06-24 2 min read

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() in onStop().
  • Use callbackFlow in 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

  1. Forgetting deriving (Show, Eq) on custom data types needed for debugging
  2. Placing the wildcard pattern first in case expressions, making all subsequent patterns unreachable
  3. Using head and tail instead 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

### How do I know if data is from cache or server?

Check snapshot.metadata.isFromCache. During offline or initial load, it's true. When the server updates arrive, the listener fires again with isFromCache = false.

### What happens if the network is offline?

Firestore uses local persistence (if enabled). The listener fires with cached data. When online, it syncs and fires with fresh data. No manual reconnection needed.

### Can I have multiple listeners on the same query?

Yes, but each listener counts as a separate connection. For the same data in multiple places, share a single StateFlow from the Repository.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro