Obtén actualizaciones en tiempo real con Cloud Firestore

Puedes escuchar un documento con el método onSnapshot() . Una llamada inicial utilizando la devolución de llamada que usted proporciona crea una instantánea del documento inmediatamente con el contenido actual del documento único. Luego, cada vez que cambia el contenido, otra llamada actualiza la instantánea del documento.

import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
    console
.log("Current data: ", doc.data());
});
db.collection("cities").doc("SF")
   
.onSnapshot((doc) => {
        console
.log("Current data: ", doc.data());
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
db.collection("cities").document("SF")
 
.addSnapshotListener { documentSnapshot, error in
    guard
let document = documentSnapshot else {
      print
("Error fetching document: \(error!)")
     
return
   
}
    guard
let data = document.data() else {
      print
("Document data was empty.")
     
return
   
}
    print
("Current data: \(data)")
 
}
Nota: Este producto no está disponible en destinos watchOS y App Clip.
[[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]
    addSnapshotListener
:^(FIRDocumentSnapshot *snapshot, NSError *error) {
     
if (snapshot == nil) {
       
NSLog(@"Error fetching document: %@", error);
       
return;
     
}
     
NSLog(@"Current data: %@", snapshot.data);
   
}];
val docRef = db.collection("cities").document("SF")
docRef
.addSnapshotListener { snapshot, e ->
   
if (e != null) {
       
Log.w(TAG, "Listen failed.", e)
       
return@addSnapshotListener
   
}

   
if (snapshot != null && snapshot.exists()) {
       
Log.d(TAG, "Current data: ${snapshot.data}")
   
} else {
       
Log.d(TAG, "Current data: null")
   
}
}
final DocumentReference docRef = db.collection("cities").document("SF");
docRef
.addSnapshotListener(new EventListener<DocumentSnapshot>() {
   
@Override
   
public void onEvent(@Nullable DocumentSnapshot snapshot,
                       
@Nullable FirebaseFirestoreException e) {
       
if (e != null) {
           
Log.w(TAG, "Listen failed.", e);
           
return;
       
}

       
if (snapshot != null && snapshot.exists()) {
           
Log.d(TAG, "Current data: " + snapshot.getData());
       
} else {
           
Log.d(TAG, "Current data: null");
       
}
   
}
});
final docRef = db.collection("cities").doc("SF");
docRef
.snapshots().listen(
     
(event) => print("current data: ${event.data()}"),
      onError
: (error) => print("Listen failed: $error"),
   
);

A menudo, desea que su interfaz de usuario reaccione a los cambios en el contenido de un documento o colección de Firestore. Puedes hacerlo con un widget StreamBuilder que consume la secuencia de instantáneas de Firestore:

class UserInformation extends StatefulWidget {
 
@override
  _UserInformationState createState
() => _UserInformationState();
}

class _UserInformationState extends State<UserInformation> {
 
final Stream<QuerySnapshot> _usersStream =
     
FirebaseFirestore.instance.collection('users').snapshots();

 
@override
 
Widget build(BuildContext context) {
   
return StreamBuilder<QuerySnapshot>(
      stream
: _usersStream,
      builder
: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
       
if (snapshot.hasError) {
         
return const Text('Something went wrong');
       
}

       
if (snapshot.connectionState == ConnectionState.waiting) {
         
return const Text("Loading");
       
}

       
return ListView(
          children
: snapshot.data!.docs
             
.map((DocumentSnapshot document) {
               
Map<String, dynamic> data =
                    document
.data()! as Map<String, dynamic>;
               
return ListTile(
                  title
: Text(data['full_name']),
                  subtitle
: Text(data['company']),
               
);
             
})
             
.toList()
             
.cast(),
       
);
     
},
   
);
 
}
}
DocumentReference docRef = db.collection("cities").document("SF");
docRef
.addSnapshotListener(
   
new EventListener<DocumentSnapshot>() {
     
@Override
     
public void onEvent(@Nullable DocumentSnapshot snapshot, @Nullable FirestoreException e) {
       
if (e != null) {
         
System.err.println("Listen failed: " + e);
         
return;
       
}

       
if (snapshot != null && snapshot.exists()) {
         
System.out.println("Current data: " + snapshot.getData());
       
} else {
         
System.out.print("Current data: null");
       
}
     
}
   
});

# Create an Event for notifying main thread.
callback_done
= threading.Event()

# Create a callback on_snapshot function to capture changes
def on_snapshot(doc_snapshot, changes, read_time):
   
for doc in doc_snapshot:
       
print(f"Received document snapshot: {doc.id}")
    callback_done
.set()

doc_ref
= db.collection("cities").document("SF")

# Watch the document
doc_watch
= doc_ref.on_snapshot(on_snapshot)
DocumentReference doc_ref = db->Collection("cities").Document("SF");
doc_ref
.AddSnapshotListener(
   
[](const DocumentSnapshot& snapshot, Error error, const std::string& errorMsg) {
     
if (error == Error::kErrorOk) {
       
if (snapshot.exists()) {
          std
::cout << "Current data: " << snapshot << std::endl;
       
} else {
          std
::cout << "Current data: null" << std::endl;
       
}
     
} else {
        std
::cout << "Listen failed: " << error << std::endl;
     
}
   
});
const doc = db.collection('cities').doc('SF');

const observer = doc.onSnapshot(docSnapshot => {
  console
.log(`Received doc snapshot: ${docSnapshot}`);
 
// ...
}, err => {
  console
.log(`Encountered error: ${err}`);
});
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.
func listenDocument
(ctx context.Context, w io.Writer, projectID, collection string) error {
       
// projectID := "project-id"
       
// Сontext with timeout stops listening to changes.
        ctx
, cancel := context.WithTimeout(ctx, 30*time.Second)
        defer cancel
()

        client
, err := firestore.NewClient(ctx, projectID)
       
if err != nil {
               
return fmt.Errorf("firestore.NewClient: %w", err)
       
}
        defer client
.Close()

        it
:= client.Collection(collection).Doc("SF").Snapshots(ctx)
       
for {
                snap
, err := it.Next()
               
// DeadlineExceeded will be returned when ctx is cancelled.
               
if status.Code(err) == codes.DeadlineExceeded {
                       
return nil
               
}
               
if err != nil {
                       
return fmt.Errorf("Snapshots.Next: %w", err)
               
}
               
if !snap.Exists() {
                        fmt
.Fprintf(w, "Document no longer exists\n")
                       
return nil
               
}
                fmt
.Fprintf(w, "Received document snapshot: %v\n", snap.Data())
       
}
}
// Not supported in the PHP client library
DocumentReference docRef = 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> pair in city) {
       
Debug.Log(String.Format("{0}: {1}", pair.Key, pair.Value));
   
}
});
DocumentReference docRef = db.Collection("cities").Document("SF");
FirestoreChangeListener listener = 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> pair in city)
       
{
           
Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
       
}
   
}
});
doc_ref = firestore.col(collection_path).doc document_path
snapshots
= []

# Watch the document.
listener
= doc_ref.listen do |snapshot|
  puts
"Received document snapshot: #{snapshot.document_id}"
  snapshots
<< snapshot
end

Eventos para cambios locales.

Las escrituras locales en su aplicación invocarán a los oyentes de instantáneas de inmediato. Esto se debe a una característica importante llamada "compensación de latencia". Cuando realiza una escritura, sus oyentes serán notificados con los nuevos datos antes de que se envíen al backend.

Los documentos recuperados tienen una propiedad metadata.hasPendingWrites que indica si el documento tiene cambios locales que aún no se han escrito en el backend. Puede utilizar esta propiedad para determinar el origen de los eventos recibidos por su escucha de instantáneas:

import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
 
const source = doc.metadata.hasPendingWrites ? "Local" : "Server";
  console
.log(source, " data: ", doc.data());
});
db.collection("cities").doc("SF")
   
.onSnapshot((doc) => {
       
var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
        console
.log(source, " data: ", doc.data());
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
db.collection("cities").document("SF")
 
.addSnapshotListener { documentSnapshot, error in
    guard
let document = documentSnapshot else {
      print
("Error fetching document: \(error!)")
     
return
   
}
   
let source = document.metadata.hasPendingWrites ? "Local" : "Server"
    print
("\(source) data: \(document.data() ?? [:])")
 
}
Nota: Este producto no está disponible en destinos watchOS y App Clip.
[[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]
    addSnapshotListener
:^(FIRDocumentSnapshot *snapshot, NSError *error) {
     
if (snapshot == nil) {
       
NSLog(@"Error fetching document: %@", error);
       
return;
     
}
     
NSString *source = snapshot.metadata.hasPendingWrites ? @"Local" : @"Server";
     
NSLog(@"%@ data: %@", source, snapshot.data);
   
}];
val docRef = db.collection("cities").document("SF")
docRef
.addSnapshotListener { snapshot, e ->
   
if (e != null) {
       
Log.w(TAG, "Listen failed.", e)
       
return@addSnapshotListener
   
}

   
val source = if (snapshot != null && snapshot.metadata.hasPendingWrites()) {
       
"Local"
   
} else {
       
"Server"
   
}

   
if (snapshot != null && snapshot.exists()) {
       
Log.d(TAG, "$source data: ${snapshot.data}")
   
} else {
       
Log.d(TAG, "$source data: null")
   
}
}
final DocumentReference docRef = db.collection("cities").document("SF");
docRef
.addSnapshotListener(new EventListener<DocumentSnapshot>() {
   
@Override
   
public void onEvent(@Nullable DocumentSnapshot snapshot,
                       
@Nullable FirebaseFirestoreException e) {
       
if (e != null) {
           
Log.w(TAG, "Listen failed.", e);
           
return;
       
}

       
String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
               
? "Local" : "Server";

       
if (snapshot != null && snapshot.exists()) {
           
Log.d(TAG, source + " data: " + snapshot.getData());
       
} else {
           
Log.d(TAG, source + " data: null");
       
}
   
}
});
final docRef = db.collection("cities").doc("SF");
docRef
.snapshots().listen(
 
(event) {
   
final source = (event.metadata.hasPendingWrites) ? "Local" : "Server";
   
print("$source data: ${event.data()}");
 
},
  onError
: (error) => print("Listen failed: $error"),
);
# Not yet supported in the Java client library
// Not yet supported in Python client library
DocumentReference doc_ref = db->Collection("cities").Document("SF");
doc_ref
.AddSnapshotListener([](const DocumentSnapshot& snapshot,
                               
Error error, const std::string& errorMsg) {
 
if (error == Error::kErrorOk) {
   
const char* source =
        snapshot
.metadata().has_pending_writes() ? "Local" : "Server";
   
if (snapshot.exists()) {
      std
::cout << source << " data: " << snapshot.Get("name").string_value()
               
<< std::endl;
   
} else {
      std
::cout << source << " data: null" << std::endl;
   
}
 
} else {
    std
::cout << "Listen failed: " << error << std::endl;
 
}
});
// Not yet supported in the Node.js client library
// Not yet supported in the Go client library
// Not supported in the PHP client library
DocumentReference docRef = db.Collection("cities").Document("SF");
docRef
.Listen(
  snapshot
=>
 
{
     
string source = (snapshot != null && snapshot.Metadata.HasPendingWrites) ? "Local" : "Server";
     
string snapshotData = "null";
     
if (snapshot != null && snapshot.Exists)
     
{
         
System.Text.StringBuilder builder = new System.Text.StringBuilder();
         
IDictionary<string, object> dict = snapshot.ToDictionary();
         
foreach (var KVPair in dict)
         
{
              builder
.Append($"{KVPair.Key}: {KVPair.Value}\n");
         
}
          snapshotData
= builder.ToString();
     
}
     
Debug.Log($"{source} data: ${snapshotData}");
 
});
// Not yet supported in the C# client library
// Not yet supported in the Ruby client library

Eventos para cambios de metadatos

Al escuchar cambios en un documento, colección o consulta, puede pasar opciones para controlar la granularidad de los eventos que recibirá su oyente.

De forma predeterminada, los oyentes no reciben notificaciones de los cambios que solo afectan a los metadatos. Considere lo que sucede cuando su aplicación escribe un documento nuevo:

  1. Se activa inmediatamente un evento de cambio con los nuevos datos. El documento aún no se ha escrito en el servidor, por lo que el indicador "escrituras pendientes" es true .
  2. El documento se escribe en el backend.
  3. El backend notifica al cliente sobre la escritura exitosa. No hay cambios en los datos del documento, pero sí en los metadatos porque el indicador "escrituras pendientes" ahora es false .

Si desea recibir eventos instantáneos cuando el documento o los metadatos de la consulta cambien, pase un objeto de opciones de escucha al adjuntar su oyente:

import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(
  doc
(db, "cities", "SF"),
 
{ includeMetadataChanges: true },
 
(doc) => {
   
// ...
 
});
db.collection("cities").doc("SF")
   
.onSnapshot({
       
// Listen for document metadata changes
        includeMetadataChanges
: true
   
}, (doc) => {
       
// ...
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
// Listen to document metadata.
db.collection("cities").document("SF")
 
.addSnapshotListener(includeMetadataChanges: true) { documentSnapshot, error in
   
// ...
  }
Nota: Este producto no está disponible en destinos watchOS y App Clip.
// Listen for metadata changes.
[[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]
    addSnapshotListenerWithIncludeMetadataChanges
:YES
                                         listener
:^(FIRDocumentSnapshot *snapshot, NSError *error) {
   
// ...
}];
// Listen for metadata changes to the document.
val docRef = db.collection("cities").document("SF")
docRef
.addSnapshotListener(MetadataChanges.INCLUDE) { snapshot, e ->
   
// ...
}
// Listen for metadata changes to the document.
DocumentReference docRef = db.collection("cities").document("SF");
docRef
.addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<DocumentSnapshot>() {
   
@Override
   
public void onEvent(@Nullable DocumentSnapshot snapshot,
                       
@Nullable FirebaseFirestoreException e) {
       
// ...
   
}
});
final docRef = db.collection("cities").doc("SF");
docRef
.snapshots(includeMetadataChanges: true).listen((event) {
 
// ...
});
// Not yet supported in the Java client library
// Not yet supported in Python client library
DocumentReference doc_ref = db->Collection("cities").Document("SF");
doc_ref
.AddSnapshotListener(
   
MetadataChanges::kInclude,
   
[](const DocumentSnapshot& snapshot, Error error, const std::string& errorMsg) { /* ... */ });
// Not yet supported the Node.js client library
// Not yet supported in the Go client library
// Not supported in the PHP client library
DocumentReference docRef = db.Collection("cities").Document("SF");
docRef
.Listen(MetadataChanges.Include, snapshot =>
{
   
// ...
});
// Not yet supported in the C# client library
// Not yet supported in the Ruby client library

Escuche varios documentos en una colección

Al igual que con los documentos, puedes usar onSnapshot() en lugar de get() para escuchar los resultados de una consulta. Esto crea una instantánea de consulta. Por ejemplo, para escuchar los documentos con el estado CA :

import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
 
const cities = [];
  querySnapshot
.forEach((doc) => {
      cities
.push(doc.data().name);
 
});
  console
.log("Current cities in CA: ", cities.join(", "));
});
db.collection("cities").where("state", "==", "CA")
   
.onSnapshot((querySnapshot) => {
       
var cities = [];
        querySnapshot
.forEach((doc) => {
            cities
.push(doc.data().name);
       
});
        console
.log("Current cities in CA: ", cities.join(", "));
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
db.collection("cities").whereField("state", isEqualTo: "CA")
 
.addSnapshotListener { querySnapshot, error in
    guard
let documents = querySnapshot?.documents else {
      print
("Error fetching documents: \(error!)")
     
return
   
}
   
let cities = documents.compactMap { $0["name"] }
    print
("Current cities in CA: \(cities)")
 
}
Nota: Este producto no está disponible en destinos watchOS y App Clip.
[[[self.db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"]
    addSnapshotListener
:^(FIRQuerySnapshot *snapshot, NSError *error) {
     
if (snapshot == nil) {
       
NSLog(@"Error fetching documents: %@", error);
       
return;
     
}
     
NSMutableArray *cities = [NSMutableArray array];
     
for (FIRDocumentSnapshot *document in snapshot.documents) {
       
[cities addObject:document.data[@"name"]];
     
}
     
NSLog(@"Current cities in CA: %@", cities);
   
}];
db.collection("cities")
   
.whereEqualTo("state", "CA")
   
.addSnapshotListener { value, e ->
       
if (e != null) {
           
Log.w(TAG, "Listen failed.", e)
           
return@addSnapshotListener
       
}

       
val cities = ArrayList<String>()
       
for (doc in value!!) {
            doc
.getString("name")?.let {
                cities
.add(it)
           
}
       
}
       
Log.d(TAG, "Current cites in CA: $cities")
   
}
db.collection("cities")
       
.whereEqualTo("state", "CA")
       
.addSnapshotListener(new EventListener<QuerySnapshot>() {
           
@Override
           
public void onEvent(@Nullable QuerySnapshot value,
                               
@Nullable FirebaseFirestoreException e) {
               
if (e != null) {
                   
Log.w(TAG, "Listen failed.", e);
                   
return;
               
}

               
List<String> cities = new ArrayList<>();
               
for (QueryDocumentSnapshot doc : value) {
                   
if (doc.get("name") != null) {
                        cities
.add(doc.getString("name"));
                   
}
               
}
               
Log.d(TAG, "Current cites in CA: " + cities);
           
}
       
});
db
   
.collection("cities")
   
.where("state", isEqualTo: "CA")
   
.snapshots()
   
.listen((event) {
 
final cities = [];
 
for (var doc in event.docs) {
    cities
.add(doc.data()["name"]);
 
}
 
print("cities in CA: ${cities.join(", ")}");
});
db.collection("cities")
   
.whereEqualTo("state", "CA")
   
.addSnapshotListener(
       
new EventListener<QuerySnapshot>() {
         
@Override
         
public void onEvent(
             
@Nullable QuerySnapshot snapshots, @Nullable FirestoreException e) {
           
if (e != null) {
             
System.err.println("Listen failed:" + e);
             
return;
           
}

           
List<String> cities = new ArrayList<>();
           
for (DocumentSnapshot doc : snapshots) {
             
if (doc.get("name") != null) {
                cities
.add(doc.getString("name"));
             
}
           
}
           
System.out.println("Current cites in CA: " + cities);
         
}
       
});

# Create an Event for notifying main thread.
callback_done
= threading.Event()

# Create a callback on_snapshot function to capture changes
def on_snapshot(col_snapshot, changes, read_time):
   
print("Callback received query snapshot.")
   
print("Current cities in California:")
   
for doc in col_snapshot:
       
print(f"{doc.id}")
    callback_done
.set()

col_query
= db.collection("cities").where(filter=FieldFilter("state", "==", "CA"))

# Watch the collection query
query_watch
= col_query.on_snapshot(on_snapshot)
db->Collection("cities")
   
.WhereEqualTo("state", FieldValue::String("CA"))
   
.AddSnapshotListener([](const QuerySnapshot& snapshot, Error error, const std::string& errorMsg) {
     
if (error == Error::kErrorOk) {
        std
::vector<std::string> cities;
        std
::cout << "Current cities in CA: " << error << std::endl;
       
for (const DocumentSnapshot& doc : snapshot.documents()) {
          cities
.push_back(doc.Get("name").string_value());
          std
::cout << "" << cities.back() << std::endl;
       
}
     
} else {
        std
::cout << "Listen failed: " << error << std::endl;
     
}
   
});
const query = db.collection('cities').where('state', '==', 'CA');

const observer = query.onSnapshot(querySnapshot => {
  console
.log(`Received query snapshot of size ${querySnapshot.size}`);
 
// ...
}, err => {
  console
.log(`Encountered error: ${err}`);
});
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.
func listenMultiple
(ctx context.Context, w io.Writer, projectID, collection string) error {
       
// projectID := "project-id"
        ctx
, cancel := context.WithTimeout(ctx, 30*time.Second)
        defer cancel
()

        client
, err := firestore.NewClient(ctx, projectID)
       
if err != nil {
               
return fmt.Errorf("firestore.NewClient: %w", err)
       
}
        defer client
.Close()

        it
:= client.Collection(collection).Where("state", "==", "CA").Snapshots(ctx)
       
for {
                snap
, err := it.Next()
               
// DeadlineExceeded will be returned when ctx is cancelled.
               
if status.Code(err) == codes.DeadlineExceeded {
                       
return nil
               
}
               
if err != nil {
                       
return fmt.Errorf("Snapshots.Next: %w", err)
               
}
               
if snap != nil {
                       
for {
                                doc
, err := snap.Documents.Next()
                               
if err == iterator.Done {
                                       
break
                               
}
                               
if err != nil {
                                       
return fmt.Errorf("Documents.Next: %w", err)
                               
}
                                fmt
.Fprintf(w, "Current cities in California: %v\n", doc.Ref.ID)
                       
}
               
}
       
}
}
// Not supported in the PHP client library
Query query = db.Collection("cities").WhereEqualTo("State", "CA");

ListenerRegistration listener = query.Listen(snapshot => {
 
Debug.Log("Callback received query snapshot.");
 
Debug.Log("Current cities in California:");
 
foreach (DocumentSnapshot documentSnapshot in snapshot.Documents) {
   
Debug.Log(documentSnapshot.Id);
 
}
});
CollectionReference citiesRef = db.Collection("cities");
Query query = db.Collection("cities").WhereEqualTo("State", "CA");

FirestoreChangeListener listener = query.Listen(snapshot =>
{
   
Console.WriteLine("Callback received query snapshot.");
   
Console.WriteLine("Current cities in California:");
   
foreach (DocumentSnapshot documentSnapshot in snapshot.Documents)
   
{
       
Console.WriteLine(documentSnapshot.Id);
   
}
});
query = firestore.col(collection_path).where :state, :==, "CA"
docs
= []

# Watch the collection query.
listener
= query.listen do |snapshot|
  puts
"Callback received query snapshot."
  puts
"Current cities in California:"
  snapshot
.docs.each do |doc|
    puts doc
.document_id
    docs
<< doc
 
end
end

El controlador de instantáneas recibirá una nueva instantánea de la consulta cada vez que cambien los resultados de la consulta (es decir, cuando se agrega, elimina o modifica un documento).

Ver cambios entre instantáneas

A menudo resulta útil ver los cambios reales en los resultados de la consulta entre instantáneas de consulta, en lugar de simplemente utilizar la instantánea de consulta completa. Por ejemplo, es posible que desee mantener un caché a medida que se agregan, eliminan y modifican documentos individuales.

import { collection, query, where, onSnapshot } from "firebase/firestore";

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot
.docChanges().forEach((change) => {
   
if (change.type === "added") {
        console
.log("New city: ", change.doc.data());
   
}
   
if (change.type === "modified") {
        console
.log("Modified city: ", change.doc.data());
   
}
   
if (change.type === "removed") {
        console
.log("Removed city: ", change.doc.data());
   
}
 
});
});
db.collection("cities").where("state", "==", "CA")
   
.onSnapshot((snapshot) => {
        snapshot
.docChanges().forEach((change) => {
           
if (change.type === "added") {
                console
.log("New city: ", change.doc.data());
           
}
           
if (change.type === "modified") {
                console
.log("Modified city: ", change.doc.data());
           
}
           
if (change.type === "removed") {
                console
.log("Removed city: ", change.doc.data());
           
}
       
});
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
db.collection("cities").whereField("state", isEqualTo: "CA")
 
.addSnapshotListener { querySnapshot, error in
    guard
let snapshot = querySnapshot else {
      print
("Error fetching snapshots: \(error!)")
     
return
   
}
    snapshot
.documentChanges.forEach { diff in
     
if (diff.type == .added) {
        print
("New city: \(diff.document.data())")
     
}
     
if (diff.type == .modified) {
        print
("Modified city: \(diff.document.data())")
     
}
     
if (diff.type == .removed) {
        print
("Removed city: \(diff.document.data())")
     
}
   
}
 
}
Nota: Este producto no está disponible en destinos watchOS y App Clip.
[[[self.db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"]
    addSnapshotListener
:^(FIRQuerySnapshot *snapshot, NSError *error) {
     
if (snapshot == nil) {
       
NSLog(@"Error fetching documents: %@", error);
       
return;
     
}
     
for (FIRDocumentChange *diff in snapshot.documentChanges) {
       
if (diff.type == FIRDocumentChangeTypeAdded) {
         
NSLog(@"New city: %@", diff.document.data);
       
}
       
if (diff.type == FIRDocumentChangeTypeModified) {
         
NSLog(@"Modified city: %@", diff.document.data);
       
}
       
if (diff.type == FIRDocumentChangeTypeRemoved) {
         
NSLog(@"Removed city: %@", diff.document.data);
       
}
     
}
   
}];
db.collection("cities")
   
.whereEqualTo("state", "CA")
   
.addSnapshotListener { snapshots, e ->
       
if (e != null) {
           
Log.w(TAG, "listen:error", e)
           
return@addSnapshotListener
       
}

       
for (dc in snapshots!!.documentChanges) {
           
when (dc.type) {
               
DocumentChange.Type.ADDED -> Log.d(TAG, "New city: ${dc.document.data}")
               
DocumentChange.Type.MODIFIED -> Log.d(TAG, "Modified city: ${dc.document.data}")
               
DocumentChange.Type.REMOVED -> Log.d(TAG, "Removed city: ${dc.document.data}")
           
}
       
}
   
}
db.collection("cities")
       
.whereEqualTo("state", "CA")
       
.addSnapshotListener(new EventListener<QuerySnapshot>() {
           
@Override
           
public void onEvent(@Nullable QuerySnapshot snapshots,
                               
@Nullable FirebaseFirestoreException e) {
               
if (e != null) {
                   
Log.w(TAG, "listen:error", e);
                   
return;
               
}

               
for (DocumentChange dc : snapshots.getDocumentChanges()) {
                   
switch (dc.getType()) {
                       
case ADDED:
                           
Log.d(TAG, "New city: " + dc.getDocument().getData());
                           
break;
                       
case MODIFIED:
                           
Log.d(TAG, "Modified city: " + dc.getDocument().getData());
                           
break;
                       
case REMOVED:
                           
Log.d(TAG, "Removed city: " + dc.getDocument().getData());
                           
break;
                   
}
               
}

           
}
       
});
db
   
.collection("cities")
   
.where("state", isEqualTo: "CA")
   
.snapshots()
   
.listen((event) {
 
for (var change in event.docChanges) {
   
switch (change.type) {
     
case DocumentChangeType.added:
       
print("New City: ${change.doc.data()}");
       
break;
     
case DocumentChangeType.modified:
       
print("Modified City: ${change.doc.data()}");
       
break;
     
case DocumentChangeType.removed:
       
print("Removed City: ${change.doc.data()}");
       
break;
   
}
 
}
});
db.collection("cities")
   
.whereEqualTo("state", "CA")
   
.addSnapshotListener(
       
new EventListener<QuerySnapshot>() {
         
@Override
         
public void onEvent(
             
@Nullable QuerySnapshot snapshots, @Nullable FirestoreException e) {
           
if (e != null) {
             
System.err.println("Listen failed: " + e);
             
return;
           
}

           
for (DocumentChange dc : snapshots.getDocumentChanges()) {
             
switch (dc.getType()) {
               
case ADDED:
                 
System.out.println("New city: " + dc.getDocument().getData());
                 
break;
               
case MODIFIED:
                 
System.out.println("Modified city: " + dc.getDocument().getData());
                 
break;
               
case REMOVED:
                 
System.out.println("Removed city: " + dc.getDocument().getData());
                 
break;
               
default:
                 
break;
             
}
           
}
         
}
       
});
db->Collection("cities")
   
.WhereEqualTo("state", FieldValue::String("CA"))
   
.AddSnapshotListener([](const QuerySnapshot& snapshot, Error error, const std::string& errorMsg) {
     
if (error == Error::kErrorOk) {
       
for (const DocumentChange& dc : snapshot.DocumentChanges()) {
         
switch (dc.type()) {
           
case DocumentChange::Type::kAdded:
              std
::cout << "New city: "
                       
<< dc.document().Get("name").string_value() << std::endl;
             
break;
           
case DocumentChange::Type::kModified:
              std
::cout << "Modified city: "
                       
<< dc.document().Get("name").string_value() << std::endl;
             
break;
           
case DocumentChange::Type::kRemoved:
              std
::cout << "Removed city: "
                       
<< dc.document().Get("name").string_value() << std::endl;
             
break;
         
}
       
}
     
} else {
        std
::cout << "Listen failed: " << error << std::endl;
     
}
   
});

# Create an Event for notifying main thread.
delete_done
= threading.Event()

# Create a callback on_snapshot function to capture changes
def on_snapshot(col_snapshot, changes, read_time):
   
print("Callback received query snapshot.")
   
print("Current cities in California: ")
   
for change in changes:
       
if change.type.name == "ADDED":
           
print(f"New city: {change.document.id}")
       
elif change.type.name == "MODIFIED":
           
print(f"Modified city: {change.document.id}")
       
elif change.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 query
query_watch
= col_query.on_snapshot(on_snapshot)
const observer = db.collection('cities').where('state', '==', 'CA')
 
.onSnapshot(querySnapshot => {
    querySnapshot
.docChanges().forEach(change => {
     
if (change.type === 'added') {
        console
.log('New city: ', change.doc.data());
     
}
     
if (change.type === 'modified') {
        console
.log('Modified city: ', change.doc.data());
     
}
     
if (change.type === 'removed') {
        console
.log('Removed city: ', change.doc.data());
     
}
   
});
 
});
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.
func listenChanges
(ctx context.Context, w io.Writer, projectID, collection string) error {
       
// projectID := "project-id"
        ctx
, cancel := context.WithTimeout(ctx, 30*time.Second)
        defer cancel
()

        client
, err := firestore.NewClient(ctx, projectID)
       
if err != nil {
               
return fmt.Errorf("firestore.NewClient: %w", err)
       
}
        defer client
.Close()

        it
:= client.Collection(collection).Where("state", "==", "CA").Snapshots(ctx)
       
for {
                snap
, err := it.Next()
               
// DeadlineExceeded will be returned when ctx is cancelled.
               
if status.Code(err) == codes.DeadlineExceeded {
                       
return nil
               
}
               
if err != nil {
                       
return fmt.Errorf("Snapshots.Next: %w", err)
               
}
               
if snap != nil {
                       
for _, change := range snap.Changes {
                               
switch change.Kind {
                               
case firestore.DocumentAdded:
                                        fmt
.Fprintf(w, "New city: %v\n", change.Doc.Data())
                               
case firestore.DocumentModified:
                                        fmt
.Fprintf(w, "Modified city: %v\n", change.Doc.Data())
                               
case firestore.DocumentRemoved:
                                        fmt
.Fprintf(w, "Removed city: %v\n", change.Doc.Data())
                               
}
                       
}
               
}
       
}
}
// Not supported in the PHP client library
Query query = db.Collection("cities").WhereEqualTo("State", "CA");

ListenerRegistration listener = query.Listen(snapshot =>
{
   
foreach (DocumentChange change in snapshot.GetChanges())
   
{
       
if (change.ChangeType == DocumentChange.Type.Added)
       
{
           
Debug.Log(String.Format("New city: {0}", change.Document.Id));
       
}
       
else if (change.ChangeType == DocumentChange.Type.Modified)
       
{
           
Debug.Log(String.Format("Modified city: {0}", change.Document.Id));
       
}
       
else if (change.ChangeType == DocumentChange.Type.Removed)
       
{
           
Debug.Log(String.Format("Removed city: {0}", change.Document.Id));
       
}
   
}
});
CollectionReference citiesRef = db.Collection("cities");
Query query = db.Collection("cities").WhereEqualTo("State", "CA");

FirestoreChangeListener listener = query.Listen(snapshot =>
{
   
foreach (DocumentChange change in snapshot.Changes)
   
{
       
if (change.ChangeType.ToString() == "Added")
       
{
           
Console.WriteLine("New city: {0}", change.Document.Id);
       
}
       
else if (change.ChangeType.ToString() == "Modified")
       
{
           
Console.WriteLine("Modified city: {0}", change.Document.Id);
       
}
       
else if (change.ChangeType.ToString() == "Removed")
       
{
           
Console.WriteLine("Removed city: {0}", change.Document.Id);
       
}
   
}
});
query = firestore.col(collection_path).where :state, :==, "CA"
added
= []
modified
= []
removed
= []

# Watch the collection query.
listener
= query.listen do |snapshot|
  puts
"Callback received query snapshot."
  puts
"Current cities in California:"
  snapshot
.changes.each do |change|
   
if change.added?
      puts
"New city: #{change.doc.document_id}"
      added
<< snapshot
   
elsif change.modified?
      puts
"Modified city: #{change.doc.document_id}"
      modified
<< snapshot
   
elsif change.removed?
      puts
"Removed city: #{change.doc.document_id}"
      removed
<< snapshot
   
end
 
end
end

El estado inicial puede provenir directamente del servidor o de un caché local. Si hay un estado disponible en un caché local, la instantánea de la consulta se completará inicialmente con los datos almacenados en caché y luego se actualizará con los datos del servidor cuando el cliente se haya puesto al día con el estado del servidor.

Separar un oyente

Cuando ya no esté interesado en escuchar sus datos, debe desconectar su oyente para que dejen de recibir llamadas de eventos. Esto permite que el cliente deje de usar ancho de banda para recibir actualizaciones. Por ejemplo:

import { collection, onSnapshot } from "firebase/firestore";

const unsubscribe = onSnapshot(collection(db, "cities"), () => {
 
// Respond to data
 
// ...
});

// Later ...

// Stop listening to changes
unsubscribe
();
var unsubscribe = db.collection("cities")
   
.onSnapshot(() => {
     
// Respond to data
     
// ...
   
});

// Later ...

// Stop listening to changes
unsubscribe
();
Nota: Este producto no está disponible en destinos watchOS y App Clip.
let listener = db.collection("cities").addSnapshotListener { querySnapshot, error in
 
// ...
}

// ...

// Stop listening to changes
listener.remove()
Nota: Este producto no está disponible en destinos watchOS y App Clip.
id<FIRListenerRegistration> listener = [[self.db collectionWithPath:@"cities"]
    addSnapshotListener
:^(FIRQuerySnapshot *snapshot, NSError *error) {
     
// ...
}];

// ...

// Stop listening to changes
[listener remove];
val query = db.collection("cities")
val registration = query.addSnapshotListener { snapshots, e ->
   
// ...
}

// ...

// Stop listening to changes
registration
.remove()
Query query = db.collection("cities");
ListenerRegistration registration = query.addSnapshotListener(
       
new EventListener<QuerySnapshot>() {
           
// ...
       
});

// ...

// Stop listening to changes
registration
.remove();
final collection = db.collection("cities");
final listener = collection.snapshots().listen((event) {
 
// ...
});
listener
.cancel();
Query query = db.collection("cities");
ListenerRegistration registration =
    query
.addSnapshotListener(
       
new EventListener<QuerySnapshot>() {
         
// ...
       
});

// ...

// Stop listening to changes
registration
.remove();
# Terminate watch on a document
doc_watch
.unsubscribe()
// Add a listener
Query query = db->Collection("cities");
ListenerRegistration registration = query.AddSnapshotListener(
   
[](const QuerySnapshot& snapshot, Error error, const std::string& errorMsg) { /* ... */ });
// Stop listening to changes
registration
.Remove();
const unsub = db.collection('cities').onSnapshot(() => {
});

// ...

// Stop listening for changes
unsub
();
// Сontext with timeout stops listening to changes.
ctx
, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel
()
// Not supported in the PHP client library
listener.Stop();
await listener.StopAsync();
listener.stop

Manejar errores de escucha

En ocasiones, una escucha puede fallar, por ejemplo, debido a permisos de seguridad o si intentó escuchar una consulta no válida. (Obtenga más información sobre consultas válidas e inválidas ). Para manejar estos errores, puede proporcionar una devolución de llamada de error cuando adjunte su escucha de instantáneas. Después de un error, el oyente no recibirá más eventos y no es necesario desconectarlo.

import { collection, onSnapshot } from "firebase/firestore";

const unsubscribe = onSnapshot(
  collection
(db, "cities"),
 
(snapshot) => {
   
// ...
 
},
 
(error) => {
   
// ...
 
});
db.collection("cities")
   
.onSnapshot((snapshot) => {
       
// ...
   
}, (error) => {
       
// ...
   
});
Nota: Este producto no está disponible en destinos watchOS y App Clip.
db.collection("cities")
 
.addSnapshotListener { querySnapshot, error in
   
if let error = error {
      print
("Error retreiving collection: \(error)")
   
}
 
}
Nota: Este producto no está disponible en destinos watchOS y App Clip.
[[self.db collectionWithPath:@"cities"]
    addSnapshotListener
:^(FIRQuerySnapshot *snapshot, NSError *error) {
     
if (error != nil) {
       
NSLog(@"Error retreving collection: %@", error);
     
}
   
}];
db.collection("cities")
   
.addSnapshotListener { snapshots, e ->
       
if (e != null) {
           
Log.w(TAG, "listen:error", e)
           
return@addSnapshotListener
       
}

       
for (dc in snapshots!!.documentChanges) {
           
if (dc.type == DocumentChange.Type.ADDED) {
               
Log.d(TAG, "New city: ${dc.document.data}")
           
}
       
}
   
}
db.collection("cities")
       
.addSnapshotListener(new EventListener<QuerySnapshot>() {
           
@Override
           
public void onEvent(@Nullable QuerySnapshot snapshots,
                               
@Nullable FirebaseFirestoreException e) {
               
if (e != null) {
                   
Log.w(TAG, "listen:error", e);
                   
return;
               
}

               
for (DocumentChange dc : snapshots.getDocumentChanges()) {
                   
if (dc.getType() == Type.ADDED) {
                       
Log.d(TAG, "New city: " + dc.getDocument().getData());
                   
}
               
}

           
}
       
});
final docRef = db.collection("cities");
docRef
.snapshots().listen(
     
(event) => print("listener attached"),
      onError
: (error) => print("Listen failed: $error"),
   
);
db.collection("cities")
   
.addSnapshotListener(
       
new EventListener<QuerySnapshot>() {
         
@Override
         
public void onEvent(
             
@Nullable QuerySnapshot snapshots, @Nullable FirestoreException e) {
           
if (e != null) {
             
System.err.println("Listen failed: " + e);
             
return;
           
}

           
for (DocumentChange dc : snapshots.getDocumentChanges()) {
             
if (dc.getType() == Type.ADDED) {
               
System.out.println("New city: " + dc.getDocument().getData());
             
}
           
}
         
}
       
});
// Snippet coming soon
// Snippet coming soon.
db.collection('cities')
 
.onSnapshot((snapshot) => {
   
//...
 
}, (error) => {
   
//...
 
});
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.
func listenErrors
(ctx context.Context, w io.Writer, projectID, collection string) error {
       
// projectID := "project-id"
        ctx
, cancel := context.WithTimeout(ctx, 30*time.Second)
        defer cancel
()

        client
, err := firestore.NewClient(ctx, projectID)
       
if err != nil {
               
return fmt.Errorf("firestore.NewClient: %w", err)
       
}
        defer client
.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.
               
if e := status.Code(err); e == codes.Canceled || e == codes.DeadlineExceeded {
                       
return nil
               
}
               
if err != nil {
                       
return fmt.Errorf("Snapshots.Next: %w", err)
               
}
               
if snap != nil {
                       
for _, change := range snap.Changes {
                               
if change.Kind == firestore.DocumentAdded {
                                        fmt
.Fprintf(w, "New city: %v\n", change.Doc.Data())
                               
}
                       
}
               
}
       
}
}
// Not supported in the PHP client library
ListenerRegistration registration =
db
.Collection("cities").Listen(
  querySnapshot
=>
 
{
     
// ...
 
});

registration
.ListenerTask.ContinueWithOnMainThread(
    listenerTask
=>
   
{
       
if (listenerTask.IsFaulted)
       
{
           
Debug.LogError($"Listen failed: {listenerTask.Exception}");
           
// ...
           
// Handle the listener error.
           
// ...
       
}
   
});
// Snippet coming soon
listener = firestore.col(collection_path).listen do |snapshot|
  snapshot
.changes.each do |change|
    puts
"New city: #{change.doc.document_id}" if change.added?
 
end
end

# Register to be notified when unhandled errors occur.
listener
.on_error do |error|
  puts
"Listen failed: #{error.message}"
end

Que sigue