Cloud Firestore로 실시간 업데이트 가져오기

onSnapshot() 메소드로 문서를 수신 대기할 수 있습니다. 사용자가 제공하는 콜백이 최초로 호출될 때는 단일 문서의 현재 내용으로 문서 스냅샷이 즉시 생성됩니다. 그런 다음 내용이 변경될 때마다 콜백이 호출되어 문서 스냅샷을 업데이트합니다.

db.collection("cities").doc("SF")
    .onSnapshot(function(doc) {
        console.log("Current data: ", 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");
        }
    }
});
자바
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");
    }
  }
});
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
PHP
// Not yet supported in PHP client library

로컬 변경 이벤트

앱에서 로컬로 쓰면 즉시 스냅샷 리스너가 호출됩니다. 이는 '지연 시간 보정'이라는 중요한 기능 때문입니다. 쓰기를 수행하면 데이터가 백엔드로 전송되기 전에 리스너에 새 데이터가 통보됩니다.

검색된 문서의 metadata.hasPendingWrites 속성은 문서에 아직 백엔드에 기록되지 않은 로컬 변경사항이 있는지 여부를 나타냅니다. 이 속성을 사용하여 스냅샷 리스너가 수신한 이벤트의 소스를 확인할 수 있습니다.

db.collection("cities").doc("SF")
    .onSnapshot(function(doc) {
        var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
        console.log(source, " data: ", 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");
        }
    }
});
자바
# Not yet supported in the Java client library
Python
# Not yet supported in Python client library
Node.js
// Not yet supported in the Node.js client library
Go
// Not yet supported in Go client library
PHP
// Not yet supported in PHP client library

메타데이터 변경 이벤트

문서, 컬렉션 또는 쿼리 변경사항을 수신 대기할 때 리스너가 수신할 이벤트의 세분화 수준을 제어하는 옵션을 전달할 수 있습니다.

기본적으로 메타데이터에만 영향을 미치는 변경사항은 리스너에 통보되지 않습니다. 앱에서 새 문서를 작성하면 어떻게 되는지 아래에서 확인해 보세요.

  1. 새 데이터로 변경 이벤트가 즉시 시작됩니다. 문서가 아직 백엔드에 기록되지 않았으므로 '쓰기 대기 중' 플래그가 true입니다.
  2. 문서가 백엔드에 기록됩니다.
  3. 백엔드가 클라이언트에 쓰기 완료를 통보합니다. 문서 데이터가 변경되지 않았지만 '쓰기 대기 중' 플래그가 이제 false이므로 메타데이터가 변경되었습니다.

문서 또는 쿼리 메타데이터가 변경될 때 스냅샷 이벤트를 수신하려면 리스너를 연결할 때 수신 대기 옵션 개체를 전달합니다.

db.collection("cities").doc("SF")
    .onSnapshot({
        // Listen for document metadata changes
        includeMetadataChanges: true
    }, function(doc) {
        // ...
    });
Swift
// Listen to document metadata.
let options = DocumentListenOptions().includeMetadataChanges(true);

db.collection("cities").document("SF")
    .addSnapshotListener(options: options) { documentSnapshot, error in
        // ...
    }
Objective-C
// Listen for metadata changes.
FIRDocumentListenOptions *options = [[FIRDocumentListenOptions init] includeMetadataChanges:true]

[[[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]
 addSnapshotListenerWithOptions:options listener:^(FIRDocumentSnapshot *snapshot, NSError *error) {
   // ...
 }]];
Android
// Listen for metadata changes to the document.
DocumentListenOptions options = new DocumentListenOptions()
        .includeMetadataChanges();

DocumentReference docRef = db.collection("cities").document("SF");
docRef.addSnapshotListener(options, new EventListener<DocumentSnapshot>() {
    @Override
    public void onEvent(@Nullable DocumentSnapshot snapshot,
                        @Nullable FirebaseFirestoreException e) {
        // ...
    }
});
자바
# Not yet supported in the Java client library
Python
# Not yet supported in Python client library
Node.js
// Not yet supported the Node.js client library
Go
// Not yet supported in Go client library
PHP
// Not yet supported in PHP 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 (QueryDocumentSnapshot doc : value) {
                    if (doc.get("name") != null) {
                        cities.add(doc.getString("name"));
                    }
                }
                Log.d(TAG, "Current cites in CA: " + cities);
            }
        });
자바
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);
      }
    });
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
PHP
// Not yet supported in PHP 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;
                    }
                }

            }
        });
자바
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;
          }
        }
      }
    });
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
PHP
// Not yet supported in PHP 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();
자바
Query query = db.collection("cities");
ListenerRegistration registration = query.addSnapshotListener(
    new EventListener<QuerySnapshot>() {
      // ...
    });

// ...

// Stop listening to changes
registration.remove();
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
PHP
// Not yet supported in PHP 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());
                    }
                }

            }
        });
자바
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());
          }
        }
      }
    });
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
PHP
// Not yet supported in PHP client library

다음에 대한 의견 보내기...

도움이 필요하시나요? 지원 페이지를 방문하세요.