Stay organized with collectionsSave and categorize content based on your preferences.
You canlistento a document with theonSnapshot()method. An initial
call using the callback you provide creates a document snapshot immediately with
the current contents of the single document. Then, each time
the contents change, another call updates the document snapshot.
Note:This product is not available on watchOS and App Clip targets.
db.collection("cities").document("SF").addSnapshotListener{documentSnapshot,erroringuardletdocument=documentSnapshotelse{print("Error fetching document:\(error!)")return}guardletdata=document.data()else{print("Document data was empty.")return}print("Current data:\(data)")}
Often, you want your UI to react to changes in the contents of a Firestore
document or collection. You can do so with aStreamBuilderwidget that consumes the Firestore snapshot stream:
classUserInformationextendsStatefulWidget{@override_UserInformationStatecreateState()=>_UserInformationState();}class_UserInformationStateextendsState<UserInformation>{finalStream<QuerySnapshot>_usersStream=FirebaseFirestore.instance.collection('users').snapshots();@overrideWidgetbuild(BuildContextcontext){returnStreamBuilder<QuerySnapshot>(stream:_usersStream,builder:(BuildContextcontext,AsyncSnapshot<QuerySnapshot>snapshot){if(snapshot.hasError){returnconstText('Something went wrong');}if(snapshot.connectionState==ConnectionState.waiting){returnconstText("Loading");}returnListView(children:snapshot.data!.docs.map((DocumentSnapshotdocument){Map<String,dynamic>data=document.data()!asMap<String,dynamic>;returnListTile(title:Text(data['full_name']),subtitle:Text(data['company']),);}).toList().cast(),);},);}}
# Create an Event for notifying main thread.callback_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(doc_snapshot,changes,read_time):fordocindoc_snapshot:print(f"Received document snapshot:{doc.id}")callback_done.set()doc_ref=db.collection("cities").document("SF")# Watch the documentdoc_watch=doc_ref.on_snapshot(on_snapshot)
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenDocument listens to a single document.funclistenDocument(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"// Сontext with timeout stops listening to changes.ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Doc("SF").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}if!snap.Exists(){fmt.Fprintf(w,"Document no longer exists\n")returnnil}fmt.Fprintf(w,"Received document snapshot: %v\n",snap.Data())}}
DocumentReferencedocRef=db.Collection("cities").Document("SF");docRef.Listen(snapshot=>{Debug.Log("Callback received document snapshot.");Debug.Log(String.Format("Document data for {0} document:",snapshot.Id));Dictionary<string,object>city=snapshot.ToDictionary();foreach(KeyValuePair<string,object>pairincity){Debug.Log(String.Format("{0}: {1}",pair.Key,pair.Value));}});
C#
DocumentReferencedocRef=db.Collection("cities").Document("SF");FirestoreChangeListenerlistener=docRef.Listen(snapshot=>{Console.WriteLine("Callback received document snapshot.");Console.WriteLine("Document exists? {0}",snapshot.Exists);if(snapshot.Exists){Console.WriteLine("Document data for {0} document:",snapshot.Id);Dictionary<string,object>city=snapshot.ToDictionary();foreach(KeyValuePair<string,object>pairincity){Console.WriteLine("{0}: {1}",pair.Key,pair.Value);}}});
Local writes in your app will invoke snapshot listeners immediately.
This is because of an important feature called "latency compensation."
When you perform a write, your listeners will be notified with the new
databeforethe data is sent to the backend.
Retrieved documents have ametadata.hasPendingWritesproperty that indicates
whether the document has local changes that haven't been written to the backend
yet. You can use this property to determine the source of events received by
your snapshot listener:
When listening for changes to a document, collection, or query, you can pass
options to control the granularity of events that your listener will receive.
By default, listeners are not notified of changes that only affect metadata.
Consider what happens when your app writes a new document:
A change event is immediately fired with the new data. The document
has not yet been written to the backend so the "pending writes"
flag istrue.
The document is written to the backend.
The backend notifies the client of the successful write. There is no
change to the document data, but there is a metadata change because
the "pending writes" flag is nowfalse.
If you want to receive snapshot events when the document or query metadata
changes, pass a listen options object when attaching your listener.
// Listen for metadata changes to the document.valdocRef=db.collection("cities").document("SF")docRef.addSnapshotListener(MetadataChanges.INCLUDE){snapshot,e->// ...}
// Listen for metadata changes to the document.DocumentReferencedocRef=db.collection("cities").document("SF");docRef.addSnapshotListener(MetadataChanges.INCLUDE,newEventListener<DocumentSnapshot>(){@OverridepublicvoidonEvent(@NullableDocumentSnapshotsnapshot,@NullableFirebaseFirestoreExceptione){// ...}});
Cloud Firestoresnapshot listeners take an initial snapshot from the local cache
and concurrently fetch corresponding data from the server.
In some cases, you may not want follow-up fetches from the server. Client SDKs
allow you to configure listeners to fire only with respect to data in the local
cache. This helps you avoid unnecessary server calls and their costs, and
leverage the client-side cache, which reflects local data and mutations.
Here, snapshot options are set in client code to allow listening for local
changes only.
Note:This product is not available on watchOS and App Clip targets.
// Set up listener optionsletoptions=SnapshotListenOptions().withSource(ListenSource.cache).withIncludeMetadataChanges(true)db.collection("cities").document("SF").addSnapshotListener(options:options){documentSnapshot,errorin// ...}
Objective-C
Note:This product is not available on watchOS and App Clip targets.
// Set up listener optionsFIRSnapshotListenOptions*options=[[FIRSnapshotListenOptionsalloc]init];FIRSnapshotListenOptions*optionsWithSourceAndMetadata=[[optionsoptionsWithIncludeMetadataChanges:YES]optionsWithSource:FIRListenSourceCache];[[[self.dbcollectionWithPath:@"cities"]documentWithPath:@"SF"]addSnapshotListenerWithOptions:optionsWithSourceAndMetadatalistener:^(FIRDocumentSnapshot*snapshot,NSError*error){//…}];
Kotlin
// Set up listener optionsvaloptions=SnapshotListenOptions.Builder().setMetadataChanges(MetadataChanges.INCLUDE).setSource(ListenSource.CACHE).build();db.collection("cities").document("SF").addSnapshotListener(options){snapshot,error->//…}
Java
// Set up listener optionsSnapshotListenOptionsoptions=newSnapshotListenOptions.Builder().setMetadataChanges(MetadataChanges.INCLUDE).setSource(ListenSource.CACHE).build();db.collection("cities").document("SF").addSnapshotListener(options,newEventListener<DocumentSnapshot>(){//…});
Dart
// Not yet supported in this client library
Java
#NotyetsupportedintheJavaclientlibrary
Python
//NotyetsupportedinPythonclientlibrary
C++
// Not yet supported in the C++ client library
Node.js
// Not yet supported in the Node.js client library
Go
// Not yet supported in the Go client library
PHP
// Not yet supported in the PHP client library
Unity
// Not yet supported in the Unity client library
C#
// Not yet supported in the C# client library
Ruby
//NotyetsupportedintheRubyclientlibrary
Listen to multiple documents in a collection
As with documents, you can useonSnapshot()instead ofget()to listen to the
results of a query. This creates a query snapshot. For example, to listen to the
documents with stateCA:
Web
import{collection,query,where,onSnapshot}from"firebase/firestore";constq=query(collection(db,"cities"),where("state","==","CA"));constunsubscribe=onSnapshot(q,(querySnapshot)=>{constcities=[];querySnapshot.forEach((doc)=>{cities.push(doc.data().name);});console.log("Current cities in CA: ",cities.join(", "));});
db.collection("cities").whereEqualTo("state","CA").addSnapshotListener{value,e->if(e!=null){Log.w(TAG,"Listen failed.",e)return@addSnapshotListener}valcities=ArrayList<String>()for(docinvalue!!){doc.getString("name")?.let{cities.add(it)}}Log.d(TAG,"Current cites in CA:$cities")}
db.collection("cities").where("state",isEqualTo:"CA").snapshots().listen((event){finalcities=[];for(vardocinevent.docs){cities.add(doc.data()["name"]);}print("cities in CA:${cities.join(", ")}");});
# Create an Event for notifying main thread.callback_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(col_snapshot,changes,read_time):print("Callback received query snapshot.")print("Current cities in California:")fordocincol_snapshot:print(f"{doc.id}")callback_done.set()col_query=db.collection("cities").where(filter=FieldFilter("state","==","CA"))# Watch the collection queryquery_watch=col_query.on_snapshot(on_snapshot)
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/api/iterator""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenMultiple listens to a query, returning the names of all cities// for a state.funclistenMultiple(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Where("state","==","CA").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for{doc,err:=snap.Documents.Next()iferr==iterator.Done{break}iferr!=nil{returnfmt.Errorf("Documents.Next: %w",err)}fmt.Fprintf(w,"Current cities in California: %v\n",doc.Ref.ID)}}}}
Queryquery=db.Collection("cities").WhereEqualTo("State","CA");ListenerRegistrationlistener=query.Listen(snapshot=>{Debug.Log("Callback received query snapshot.");Debug.Log("Current cities in California:");foreach(DocumentSnapshotdocumentSnapshotinsnapshot.Documents){Debug.Log(documentSnapshot.Id);}});
C#
CollectionReferencecitiesRef=db.Collection("cities");Queryquery=db.Collection("cities").WhereEqualTo("State","CA");FirestoreChangeListenerlistener=query.Listen(snapshot=>{Console.WriteLine("Callback received query snapshot.");Console.WriteLine("Current cities in California:");foreach(DocumentSnapshotdocumentSnapshotinsnapshot.Documents){Console.WriteLine(documentSnapshot.Id);}});
query=firestore.col(collection_path).where:state,:==,"CA"docs=[]# Watch the collection query.listener=query.listendo|snapshot|puts"Callback received query snapshot."puts"Current cities in California:"snapshot.docs.eachdo|doc|putsdoc.document_iddocs<<docendend
The snapshot handler will receive a new query snapshot every time the query
results change (that is, when a document is added, removed, or modified).
View changes between snapshots
It is often useful to see the actual changes to query results between query
snapshots, instead of simply using the entire query snapshot. For example, you
may want to maintain a cache as individual documents are added, removed, and
modified.
# Create an Event for notifying main thread.delete_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(col_snapshot,changes,read_time):print("Callback received query snapshot.")print("Current cities in California: ")forchangeinchanges:ifchange.type.name=="ADDED":print(f"New city:{change.document.id}")elifchange.type.name=="MODIFIED":print(f"Modified city:{change.document.id}")elifchange.type.name=="REMOVED":print(f"Removed city:{change.document.id}")delete_done.set()col_query=db.collection("cities").where(filter=FieldFilter("state","==","CA"))# Watch the collection queryquery_watch=col_query.on_snapshot(on_snapshot)
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenChanges listens to a query, returning the list of document changes.funclistenChanges(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Where("state","==","CA").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for_,change:=rangesnap.Changes{switchchange.Kind{casefirestore.DocumentAdded:fmt.Fprintf(w,"New city: %v\n",change.Doc.Data())casefirestore.DocumentModified:fmt.Fprintf(w,"Modified city: %v\n",change.Doc.Data())casefirestore.DocumentRemoved:fmt.Fprintf(w,"Removed city: %v\n",change.Doc.Data())}}}}}
query=firestore.col(collection_path).where:state,:==,"CA"added=[]modified=[]removed=[]# Watch the collection query.listener=query.listendo|snapshot|puts"Callback received query snapshot."puts"Current cities in California:"snapshot.changes.eachdo|change|ifchange.added?puts"New city:#{change.doc.document_id}"added<<snapshotelsifchange.modified?puts"Modified city:#{change.doc.document_id}"modified<<snapshotelsifchange.removed?puts"Removed city:#{change.doc.document_id}"removed<<snapshotendendend
The initial state can come from the server directly, or from a local
cache. If there is state available in a local cache, the query snapshot will
be initially populated with the cached data, then updated with the
server's data when the client has caught up with the server's state.
Detach a listener
When you are no longer interested in listening to your data, you must detach
your listener so that your event callbacks stop getting called. This allows the
client to stop using bandwidth to receive updates. For example:
Web
import{collection,onSnapshot}from"firebase/firestore";constunsubscribe=onSnapshot(collection(db,"cities"),()=>{// Respond to data// ...});// Later ...// Stop listening to changesunsubscribe();
// Add a listenerQueryquery=db->Collection("cities");ListenerRegistrationregistration=query.AddSnapshotListener([](constQuerySnapshot&snapshot,Errorerror,conststd::string&errorMsg){/* ... */});// Stop listening to changesregistration.Remove();
A listen may occasionally fail — for example, due to security permissions, or if
you tried to listen on an invalid query. (Learn more aboutvalid and invalid queries.) To handle these
failures, you can provide an error callback when you attach your snapshot
listener. After an error, the listener will not receive any more events, and
there is no need to detach your listener.
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenErrors demonstrates how to handle listening errors.funclistenErrors(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Snapshots(ctx)for{snap,err:=it.Next()// Canceled will be returned when ctx is cancelled and DeadlineExceeded will// be returned when ctx reaches its deadline.ife:=status.Code(err);e==codes.Canceled||e==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for_,change:=rangesnap.Changes{ifchange.Kind==firestore.DocumentAdded{fmt.Fprintf(w,"New city: %v\n",change.Doc.Data())}}}}}
ListenerRegistrationregistration=db.Collection("cities").Listen(querySnapshot=>{// ...});registration.ListenerTask.ContinueWithOnMainThread(listenerTask=>{if(listenerTask.IsFaulted){Debug.LogError($"Listen failed: {listenerTask.Exception}");// ...// Handle the listener error.// ...}});
C#
// Snippet coming soon
Ruby
listener=firestore.col(collection_path).listendo|snapshot|snapshot.changes.eachdo|change|puts"New city:#{change.doc.document_id}"ifchange.added?endend# Register to be notified when unhandled errors occur.listener.on_errordo|error|puts"Listen failed:#{error.message}"end
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-04 UTC."],[],[],null,[]]