Firestore分页数据+快照侦听器



我现在正在与Firestore合作,在分页方面有点问题
基本上,我有一个集合(假设有10个项目),其中每个项目都有一些数据和时间戳。

现在,我正在获取前3个项目,如下所示:

Firestore.firestore()
.collection("collectionPath")
.order(by: "timestamp", descending: true)
.limit(to: 3)
.addSnapshotListener(snapshotListener())

在我的快照侦听器中,我保存快照中的最后一个文档,以便将其作为下一页的起点。

所以,在某个时候,我会请求下一页这样的项目:

Firestore.firestore()
.collection("collectionPath")
.order(by: "timestamp", descending: true)
.start(afterDocument: lastDocument)
.limit(to: 3)
.addSnapshotListener(snapshotListener2()) // Note that this is a new snapshot listener, I don't know how I could reuse the first one

现在,我的前端有从索引0到索引5的项目(总共6个)。整洁的

如果索引4处的文档现在将其时间戳更新为整个集合的最新时间戳,那么情况就会开始恶化
请记住,时间戳根据order子句确定其位置!

我所期望的是,在应用更改后,我仍然显示6个项目(并且仍然按时间戳排序)

实际情况是,在应用更改后,我只剩下5个项目,因为从第一个快照中挤出的项目不会自动添加到第二个快照中。

我是不是错过了与Firestore的寻呼?

编辑:根据要求,我在这里发布了更多代码:
这是我返回快照侦听器的函数。好吧,我用两种方法来请求第一个页面,然后我发布的第二个页面已经在上面了

private func snapshotListener() -> FIRQuerySnapshotBlock {
let index = self.index
return { querySnapshot, error in
guard let snap = querySnapshot, error == nil else {
log.error(error)
return
}
// Save the last doc, so we can later use pagination to retrieve further chats
if snap.count == self.limit {
self.lastDoc = snap.documents.last
} else {
self.lastDoc = nil
}
let offset = index * self.limit
snap.documentChanges.forEach() { diff in
switch diff.type {
case .added:
log.debug("added chat at index: (diff.newIndex), offset: (offset)")
self.tVHandler.dataManager.insert(item: Chat(dictionary: diff.document.data() as NSDictionary), at: IndexPath(row: Int(diff.newIndex) + offset, section: 0), in: nil)
case .removed:
log.debug("deleted chat at index: (diff.oldIndex), offset: (offset)")
self.tVHandler.dataManager.remove(itemAt: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), in: nil)
case .modified:
if diff.oldIndex == diff.newIndex {
log.debug("updated chat at index: (diff.oldIndex), offset: (offset)")
self.tVHandler.dataManager.update(item: Chat(dictionary: diff.document.data() as NSDictionary), at: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), in: nil)
} else {
log.debug("moved chat at index: (diff.oldIndex), offset: (offset) to index: (diff.newIndex), offset: (offset)")
self.tVHandler.dataManager.move(item: Chat(dictionary: diff.document.data() as NSDictionary), from: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), to: IndexPath(row: Int(diff.newIndex) + offset, section: 0), in: nil)
}
}
}
self.tableView?.reloadData()
}
}

因此,我再次询问我是否可以有一个快照侦听器来侦听我从Firestore 请求的多个页面中的更改

好吧,我联系了Firebase Google Group的人员寻求帮助,他们告诉我我的用例还不受支持
感谢加藤理查森处理我的问题!

对于任何对细节感兴趣的人,请参阅这个线程

我今天遇到了同样的用例,并且我已经成功地在Objective C客户端中实现了一个工作解决方案。如果有人想在他们的程序中应用,下面是算法,如果谷歌云消防商店团队能把我的解决方案放在他们的页面上,我将不胜感激。

用例:允许对最近聊天的长列表进行分页的功能,以及附加实时侦听器以更新列表以在顶部显示最新消息的选项。

解决方案:这可以通过使用分页逻辑来实现,就像我们对其他长列表所做的那样,并附加限制设置为1:的实时侦听器

步骤1:在页面加载上使用分页查询获取聊天,如下所示:

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self fetchChats];
}
-(void)fetchChats {
__weak typeof(self) weakSelf = self;
FIRQuery *paginateChatsQuery = [[[self.db collectionWithPath:MAGConstCollectionNameChats]queryOrderedByField:MAGConstFieldNameTimestamp descending:YES]queryLimitedTo:MAGConstPageLimit];
if(self.arrChats.count > 0){
FIRDocumentSnapshot *lastChatDocument = self.arrChats.lastObject;
paginateChatsQuery = [paginateChatsQuery queryStartingAfterDocument:lastChatDocument];
}
[paginateChatsQuery getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (snapshot == nil) {
NSLog(@"Error fetching documents: %@", error);
return;
}
///2. Observe chat updates if not attached
if(weakSelf.chatObserverState == ChatObserverStateNotAttached) {
weakSelf.chatObserverState = ChatObserverStateAttaching;
[weakSelf observeChats];
}
if(snapshot.documents.count < MAGConstPageLimit) {
weakSelf.noMoreData = YES;
}
else {
weakSelf.noMoreData = NO;
}
[weakSelf.arrChats addObjectsFromArray:snapshot.documents];
[weakSelf.tblVuChatsList reloadData];
}];
}

步骤2:在成功回调"fetchAlerts"方法时,只附加一次实时更新的观察者,限制设置为1。

-(void)observeChats {
__weak typeof(self) weakSelf = self;
self.chatsListener = [[[[self.db collectionWithPath:MAGConstCollectionNameChats]queryOrderedByField:MAGConstFieldNameTimestamp descending:YES]queryLimitedTo:1]addSnapshotListener:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (snapshot == nil) {
NSLog(@"Error fetching documents: %@", error);
return;
}
if(weakSelf.chatObserverState == ChatObserverStateAttaching) {
weakSelf.chatObserverState = ChatObserverStateAttached;
}
for (FIRDocumentChange *diff in snapshot.documentChanges) {
if (diff.type == FIRDocumentChangeTypeAdded) {
///New chat added
NSLog(@"Added chat: %@", diff.document.data);
FIRDocumentSnapshot *chatDoc = diff.document;
[weakSelf handleChatUpdates:chatDoc];
}
else if (diff.type == FIRDocumentChangeTypeModified) {
NSLog(@"Modified chat: %@", diff.document.data);
FIRDocumentSnapshot *chatDoc = diff.document;
[weakSelf handleChatUpdates:chatDoc];
}
else if (diff.type == FIRDocumentChangeTypeRemoved) {
NSLog(@"Removed chat: %@", diff.document.data);
}
}
}];
}

步骤3。在侦听器回调时,检查文档更改,仅处理FIRDocumentChangeTypeAddedFIRDocumentChangeTypeModified事件,忽略FIRDocumentChangeTypeRemovedIRDocumentChangeTypeAddedFIRDocumentChangeTypeModified事件调用">handleChatUpdates"方法来实现这一点,在这两个事件中,我们首先尝试从本地列表中查找匹配的聊天文档,如果它存在,我们将从列表中删除它,然后添加从侦听器回调接收的新文档并添加它到列表的开头。

-(void)handleChatUpdates:(FIRDocumentSnapshot *)chatDoc {
NSInteger chatIndex = [self getIndexOfMatchingChatDoc:chatDoc];
if(chatIndex != NSNotFound) {
///Remove this object
[self.arrChats removeObjectAtIndex:chatIndex];
}
///Insert this chat object at the beginning of the array
[self.arrChats insertObject:chatDoc atIndex:0];
///Refresh the tableview
[self.tblVuChatsList reloadData];
}
-(NSInteger)getIndexOfMatchingChatDoc:(FIRDocumentSnapshot *)chatDoc {
NSInteger chatIndex = 0;
for (FIRDocumentSnapshot *chatDocument in self.arrChats) {
if([chatDocument.documentID isEqualToString:chatDoc.documentID]) {
return chatIndex;
}
chatIndex++;
}
return NSNotFound;
}

步骤4。重新加载表视图以查看更改。

我的解决方案是创建一个maintainer查询-监听器来观察第一个查询中删除的项目,每次有新消息出现时我们都会更新它。

要首先使用快照侦听器进行分页,我们必须从集合中创建reference point document。之后,我们将收听基于reference point document的集合。

让我们有一个名为messages的集合和名为createdAt的时间戳,其中包含该集合中的每个文档。

//get messages
getMessages(){
//first we will fetch the very last/latest document.
//to hold listeners
listnerArray=[];
const very_last_document= await this.afs.collectons('messages')
.ref
.limit(1)
.orderBy('createdAt','desc')
.get({ source: 'server' });

//if very_last.document.empty property become true,which means there is no messages 
//present till now ,we can go with a query without having a limit
//else we have to apply the limit
if (!very_last_document.empty) {

const start = very_last_document.docs[very_last_document.docs.length - 1].data().createdAt;
//listner for new messages
//all new message will be registered on this listener
const listner_1 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.endAt(start)     <== this will make sure the query will fetch up to 'start' point(including 'start' point document)
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
},
err => {
//on error
})
//old message will be registered on this listener
const listner_2 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.limit(20)
.startAfter(start)   <== this will make sure the query will fetch after the 'start' point
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
this.listenerArray.push(listner_1, listner_2);
},
err => {
//on error
})
} else {
//no document found!
//very_last_document.empty = true
const listner_1 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
},
err => {
//on error
})
this.listenerArray.push(listner_1);
}
}

//to load more messages
LoadMoreMessage(){
//Assuming messages array holding the the message we have fetched

//getting the last element from the array messages.
//that will be the starting point of our next batch
const endAt = this.messages[this.messages.length-1].createdAt
const listner_2 = this.getService
.collections('messages')
.ref
.limit(20)
.orderBy('createdAt', "asc")    <== should be in 'asc' order
.endBefore(endAt)    <== Getting the 20 documnents (the limit we have applied) from the point 'endAt';
.onSnapshot(messages => {
if (messages.empty && this.messages.length)
this.messages[this.messages.length - 1].hasMore = false;
for (const message of messages.docChanges()) {
if (message.type === "added") 
//do the job...
if (message.type === "modified")
//do the job
if (message.type === "removed")
//do the job
}
},
err => {
//on error
})
this.listenerArray.push(listner_2)

}

最新更新