Ios Firestore分页数据&x2B;快照侦听器

Ios Firestore分页数据&x2B;快照侦听器,ios,objective-c,swift,pagination,google-cloud-firestore,Ios,Objective C,Swift,Pagination,Google Cloud Firestore,我现在在Firestore工作,在分页方面有点问题。 基本上,我有一个集合(假设有10个项),其中每个项都有一些数据和一个时间戳 现在,我正在获取前3项,如下所示: Firestore.firestore() .collection("collectionPath") .order(by: "timestamp", descending: true) .limit(to: 3) .addSnapshotListener(snapshotListener()) 在

我现在在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 client中实现了一个工作解决方案。下面是算法,如果有人想在他们的程序中应用,如果谷歌云firestore团队能够将我的解决方案放到他们的页面上,我将非常感激

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

解决方案:这可以通过使用分页逻辑来实现,就像我们对其他长列表所做的那样,并附加限制设置为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];
    }];
}
第三步。在侦听器回调时,检查文档更改并仅处理添加的FirDocumentChangeTypeAddFIRDocumentChangeTypeModified事件,并忽略移除的FIRDocumentChangeTypeRemoved事件。我们通过调用“handleChatUpdates来实现此目的方法,用于FIRDocumentChangeTypeAddedFIRDocumentChangeTypeModified事件,在该事件中,我们首先尝试从本地列表中查找匹配的聊天文档,如果它存在,我们将从列表中删除它,然后添加从侦听器回调接收的新文档并将其添加到开头在名单上

-(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;
}

第四步。重新加载tableview以查看更改。

我的解决方案是创建一个maintainer query-listener来观察从第一个查询中删除的项目,我们将在每次有新消息出现时更新它。

要首先使用快照侦听器进行分页,我们必须从集合中创建
参考点文档
。之后,我们将根据该
参考点文档
来侦听集合

让我们为该集合中的每个文档创建一个名为
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)



}
//获取消息
getMessages(){
//首先,我们将获取最新/最新的文档。
//吸引听众
listnerArray=[];
const very_last_document=等待此.afs.collections('消息')
参考号
.限额(1)
.orderBy('createdAt','desc')
.get({source:'server'});
//如果very_last.document.empty属性变为true,则表示没有消息
//到目前为止,我们可以不受限制地进行查询
//否则我们就要实行限额
如果(!very_last_document.empty){
const start=very_last_document.docs[very_last_document.docs.length-1].data().createdAt;
//新消息的listner
//所有新消息都将在此侦听器上注册
const listner_1=this.afs.collections('消息')
参考号
.orderBy('createdAt','desc')
.endAt(开始){
for(const message of messages.docChanges()){
如果(消息类型==“已添加”)
//做这项工作。。。
如果(message.type==“已修改”)
//做这项工作。。。
如果(message.type==“已删除”)
//做这项工作。。。。
}
},
错误=>{
//论错误
})
//旧消息将在此侦听器上注册
const listner_2=this.afs.collections('消息')
参考号
.orderBy('createdAt','desc')
.限额(20)
.startAfter(开始){
for(const message of messages.docChanges()){
如果(消息类型==“已添加”)
//做这项工作。。。
如果(message.type==“已修改”)
//做这项工作。。。
如果(message.type==“已删除”)
//做这项工作。。。。
}
this.listenerArray.push(listner_1,listner_2);
},
错误=>{
//论错误
})
}否则{
//没有找到文件!
//very_last_document.empty=true
const listner_1=this.afs.collections('消息')
参考号
.orderBy('createdAt','desc')
.onSnapshot(消息=>{
for(const message of messages.docChanges()){
如果(消息类型==“已添加”)
//做这项工作。。。
如果(message.type==“已修改”)
//做这项工作。。。
//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)



}