Firestore Pagination 获取下一批数据

Firestore Pagination get next batch of data

我有这个功能可以从集合中获取文档。我从 Firestore 文档中得到了这个逻辑,但我不明白 addSnapshotListener.

的目的

在真实场景中我需要:

首先 运行 第一个查询,获取最后一个文档并将其传递给下一个查询等等,对吗? 所以这意味着在每次查询之后我还需要 return 最后一个文档? 如果是这样,为什么我需要 addSnapshotListener?我也可以从 .getDocuments()

得到 lastSnapshot
class HomeViewModel: ObservableObject, LoadProtocol {
    var firestoreService: FirestoreService = FirestoreService()
    @Published var items: [Item] = []

    
    let first: Query = Firestore.firestore().collection("items").limit(to: 1)
    var next: Query = Firestore.firestore().collection("items").limit(to: 1)
    
    init() {
        self.first.addSnapshotListener { (snapshot, error) in
            
            guard let snapshot = snapshot else {
                print("Error retrieving cities: \(error.debugDescription)")
                return
            }

            guard let lastSnapshot = snapshot.documents.last else {
                // The collection is empty.
                return
            }

            // Construct a new query starting after this document,
            // retrieving the next 25 cities.
            self.next = self.firestoreService.db.collection("items")
                .start(afterDocument: lastSnapshot)

            // Use the query for pagination.
 
        }
    }
    
    func refresh() {
        self.firestoreService.fetchCollection(query: self.next) { (result: Result<[Item], Error>) in
            switch result {
            case .success(let items):
                self.items += items
                self.addToCategories()
            case .failure(let error):
                print(error)
            }
        }
    }
}

问题是您在实现时将基本游标分页与查询游标分页混合在一起。

您的代码的基本光标分页部分(直到 first.addSnapshotListener)是正确的,每次调用该函数并解析它时,每次都会执行返回越来越多的数据到您的地图,但是查询Cursor Pagination 部分将永远不会被调用,因此您可以从您的实现中完全删除该部分。

因此您的代码应如下所示:

func fetchCollection<Collection: CollectionProtocol>(lastSnapshot: DocumentSnapshot?, query: Query, completion: @escaping (Result<[Collection], Error>) -> Void) {
    var first = query.limit(to: 1)

    if let lastSnapshot = lastSnapshot {
        first = query.start(afterDocument: lastSnapshot)
    }

    first.getDocuments() { (querySnapshot, error) in
        if let snapshot = querySnapshot {
            // print(snapshot.metadata.isFromCache)
            completion(.success(snapshot.documents.map { document -> Collection in
                return Collection(document: document)
            }))
        } else if let error = error {
            completion(.failure(error))
        }
    }
}

注意:您也可以将 .limit(to:1) 添加到您的 lastSnapshot 检查中,因为现在是这样,它会检索所有文档,当然,如果这是您的预期逻辑,请忽略此注释。


编辑:

如果你想使用查询游标分页,你可以按照这个例子,其中侦听器位于 fetchCollection 函数之外,每次触发 getDocuments() 时都会执行它并安装要执行的新查询:

//initial state of your app
var results = "nil";

//initial query
var query = db.collection("foo").document("bar").limit(to: 1);

//everytime you need more data fetched and on database updates to your snapshot this will be triggered
query.addSnapshotListener { querySnapshot, error in
    guard let snapshot = querySnapshot else {
        print("Error fetching snapshots: \(error!)")
        return
    }
    snapshot.documentChanges.forEach { 
        // update data
    }
    let next = db.collection("foo").document("bar").start(afterDocument: result.last!).limit(to: 1);
    query = next;
};

//initial load of data
fetchCollection();

//simply executes the query
func fetchCollection() {
    query.getDocuments { (document, error) in
        if let document = document, document.exists {
            if(result == "nil"){
                result = document.data().map();
            }else{
                result.map({ document.data() })
            }
        } else {
            print("Document does not exist")
        }

    }
}

注意:请记住,这是一个未经测试的示例,但它可能是您在应用程序中需要的一个很好的起点,另外,我使用这个 documentation 对于侦听器的实时更新,您可能会找到更多相关信息 link。