Cloud Firestore でリアルタイム アップデートを入手する

onSnapshot() メソッドを使用すると、ドキュメントをリッスンすることができます。コールバックを使用した最初の呼び出しでは、単一のドキュメントの現在の内容ですぐにドキュメント スナップショットが作成されます。その後は、コンテンツが変更されるたびに、別の呼び出しによってドキュメント スナップショットが更新されます。

ウェブ
db.collection("cities").doc("SF")
    .onSnapshot(function(doc) {
        console.log("Current data: ", doc && doc.data());
    });
Swift
db.collection("cities").document("SF")
    .addSnapshotListener { documentSnapshot, error in
      guard let document = documentSnapshot else {
        print("Error fetching document: \(error!)")
        return
      }
      print("Current data: \(document.data())")
    }
Objective-C
[[[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);
    }];
  
Android
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");
        }
    }
});
Java
// Not yet supported in Java client library
Python
# Not yet supported in Python client library
Node.js
var doc = db.collection('cities').doc('SF');

var observer = doc.onSnapshot(docSnapshot => {
    console.log(`Received doc snapshot: ${docSnapshot}`);
    // ...
}, err => {
    console.log(`Encountered error: ${err}`);
});
Go
// Not yet supported in Go client library

ローカル変更のイベント

前の例では、サンフランシスコの人口を 999999 に設定した後、コールバックが 2 回呼び出されました。これは「レイテンシ補正」という重要な機能によるものです。書き込みを実行すると、データがバックエンド データベースに送信される前に、新しいデータがすぐにリスナーに通知されます。

上記のように、データが実際にバックエンドに書き込まれると、リスナーに再度通知されます。これには、ネットワーク接続の状態に応じて、数ミリ秒から数時間の時間がかかります。

取得されたドキュメントには、バックエンドにまだ書き込まれていないローカル変更がドキュメントにあるかどうかを示す metadata.hasPendingWrites プロパティがあります。このプロパティを使用すると、前の例を繰り返しても、2 つのイベントの違いを表示できます。

ウェブ
db.collection("cities").doc("SF")
    .onSnapshot(function(doc) {
        var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
        console.log(source, " data: ", doc && doc.data());
    });
Swift
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())")
    }
Objective-C
[[[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);
    }];
  
Android
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");
        }
    }
});
Java
// Not yet supported in Java client library
Python
# Not yet supported in Python client library
Node.js
// Not available in the Node.js client library
Go
// Not yet supported in Go client library

コレクション内の複数のドキュメントのリッスン

ドキュメントの場合と同様に、get() ではなく onSnapshot() を使用してクエリの結果をリッスンすることができます。これにより、クエリ スナップショットが作成されます。たとえば、状態が CA のドキュメントをリッスンするには、次のようにします。

ウェブ
db.collection("cities").where("state", "==", "CA")
    .onSnapshot(function(querySnapshot) {
        var cities = [];
        querySnapshot.forEach(function(doc) {
            cities.push(doc.data().name);
        });
        console.log("Current cities in CA: ", cities.join(", "));
    });
Swift
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.map { $0["name"]! }
        print("Current cities in CA: \(cities)")
    }
Objective-C
[[[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);
    }];
  
Android
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 (DocumentSnapshot doc : value) {
                    if (doc.get("name") != null) {
                        cities.add(doc.getString("name"));
                    }
                }
                Log.d(TAG, "Current cites in CA: " + cities);
            }
        });
Java
// Not yet supported in Java client library
Python
# Not yet supported in Python client library
Node.js
var query = db.collection("cities").where('state', '==', 'CA');

var observer = query.onSnapshot(querySnapshot => {
  console.log(`Received query snapshot of size ${querySnapshot.size}`);
  // ...
}, err => {
  console.log(`Encountered error: ${err}`);
});
Go
// Not yet supported in Go client library

スナップショット ハンドラは、クエリ結果が変更されるたびに(つまり、ドキュメントが追加、削除、変更されたときに)、新しいクエリ スナップショットを受け取ります。

スナップショット間の変更の表示

単純にクエリ スナップショット全体を使用するのではなく、クエリ スナップショット間でクエリ結果の実際の変更を確認すると便利な場合があります。たとえば、個々のドキュメントが追加、削除、変更されたときに、キャッシュを維持することができます。

ウェブ
db.collection("cities").where("state", "==", "CA")
    .onSnapshot(function(snapshot) {
        snapshot.docChanges.forEach(function(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());
            }
        });
    });
Swift
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())")
            }
        }
    }
Objective-C
[[[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);
        }
      }
    }];
  
Android
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;
                    }
                }

            }
        });
Java
// Not yet supported in Java client library
Python
# Not yet supported in the Python client library
Node.js
// Not yet supported in the Node.js client library
Go
// Not yet supported in Go client library

初期状態は、サーバーから直接取得することも、ローカル キャッシュから取得することもできます。ローカル キャッシュに使用可能な状態がある場合、クエリ スナップショットにはキャッシュされたデータが最初に入力され、その後、クライアントがサーバーの状態に追いついたときにサーバーのデータで更新されます。

リスナーのデタッチ

データをリッスンする必要がなくなったら、イベント コールバックが呼び出されないようにリスナーをデタッチしなければなりません。これにより、クライアントは更新を受信するための帯域幅の使用を停止することができます。onSnapshot()unsubscribe 関数を使用すると、更新のリッスンを停止できます。

ウェブ
var unsubscribe = db.collection("cities")
    .onSnapshot(function () {});
// ...
// Stop listening to changes
unsubscribe();
Swift
let listener = db.collection("cities").addSnapshotListener { querySnapshot, error in
    // ...
}

// ...

// Stop listening to changes
listener.remove()
Objective-C
id<FIRListenerRegistration> listener = [[self.db collectionWithPath:@"cities"]
    addSnapshotListener:^(FIRQuerySnapshot *snapshot, NSError *error) {
      // ...
}];

// ...

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

// ...

// Stop listening to changes
registration.remove();
Java
// Not yet supported in Java client library
Python
# Not yet supported in the Python client library
Node.js
var unsub = db.collection('cities').onSnapshot(() => {});

// ...

// Stop listening for changes
unsub();
Go
// Not yet supported in Go client library

リッスンエラーの処理

たとえば、セキュリティ権限のため、または無効なクエリでリッスンしようとした場合など、リッスンが失敗することがあります(詳細については、有効なクエリと無効なクエリをご覧ください)。これらの障害を処理するには、スナップショット リスナーをアタッチするときにエラー コールバックを提供します。エラーが発生すると、リスナーはそれ以上イベントを受信しなくなるため、リスナーをデタッチする必要はありません。

ウェブ
db.collection("cities")
    .onSnapshot(function(snapshot) {
        //...
    }, function(error) {
        //...
    });
Swift
db.collection("cities")
    .addSnapshotListener { querySnapshot, error in
        if let error = error {
            print("Error retreiving collection: \(error)")
        }
    }
Objective-C
[[self.db collectionWithPath:@"cities"]
    addSnapshotListener:^(FIRQuerySnapshot *snapshot, NSError *error) {
      if (error != nil) {
        NSLog(@"Error retreving collection: %@", error);
      }
    }];
  
Android
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());
                    }
                }

            }
        });
Java
// Not yet supported in Java client library
Python
# Not yet supported in the Python client library
Node.js
db.collection("cities")
    .onSnapshot((snapshot) => {
        //...
    }, (error) => {
        //...
    });
Go
// Not yet supported in Go client library

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。