删除某些项目后,RecyclerView中的Firestore Firebase分页无法正常工作

时间:2018-06-12 02:01:04

标签: android firebase pagination google-cloud-firestore

我有一个应用程序,用户可以在其中发布在RecyclerView中显示的项目,其中Cloud FireStore是后端。现在,在第一次启动应用程序时,它将加载前5个项目。如果RecyclerView不能再垂直滚动,它将获取另外5个项目。一切正常,除非我删除一个项目,其他项目会重复。

第一个问题场景:

首次启动时会加载5个项目,所以此刻我的列表只有5个项目。由于当我从该5个项目中删除一个项目时,我的分页限制为5,因此查询侦听器会尝试加载第6个项目。在那个部分,当我向上滚动以加载接下来的5个项目时,我将得到重复的第6个项目。

喜欢这个 1, 2, 3, 4, 5然后第3项将被删除 1, 2, 4, 5应该是结果 不幸的是,这就是我得到的1, 2, 4, 5, 6。很高兴看到查询本身在删除1项后尝试加载另外1项,但在向上滚动RecyclerView后,它将加载另外5项。那就是我得到的

1, 2, 4, 5, 6, 6, 7, 8, 9, 10

但是,因为每个新添加的项目都会显示在顶部0 index,所以这意味着我在列表中看到的是6, 1, 2, 4, 5, 6, 7, 8, 9, 10

我的想法:我是否需要在每次删除操作中更新我的DocumentSnapshot lastSeen值,还是应该动态调整limit()的值? 请告诉我处理它的最佳方法是什么。

示例代码:

 //Load the first item(s) to display
      //Set a query according to time in milliseconds
      mQuery = mDatabase.collection("Announcements")
              .orderBy("time", Query.Direction.DESCENDING)
              .limit(5);

      //Getting all documents under Announcement collection with query's condition

  annon_listener = mQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
      @Override
      public void onEvent(final QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {

          //If something went wrong
          if (e != null)
              Log.w(TAG, "Listen failed.", e);


          //If any post exist put it to model and add it to List to populate the CardView
          //If data exist in the first 5 items then item should be loaded making our 'isFirstListLoaded' variable to be true
          if (documentSnapshots != null && !documentSnapshots.isEmpty()){

              //If first item are loaded then every update post should be on the top not at the bottom
              //This can only be called once to avoid confusion/duplication getting new item
              if (isFirstListLoaded){
                  //Get the documents of last item listed in our RecyclerView
                  mLastSeen = documentSnapshots.getDocuments().get(documentSnapshots.size()-1);
                  //Clear the list first to get a latest data
                  announcementList.clear();
              }

              //Loop to read each document
              for (DocumentChange doc : documentSnapshots.getDocumentChanges()){

                  //Only added document will be read
                  switch (doc.getType()){

                      case ADDED:

                          //This can only be called once to avoid confusion getting new item(s)
                          if (isFirstListLoaded){
                              //Call the model to populate it with document
                              AnnouncementModel annonPost = doc.getDocument().toObject(AnnouncementModel.class)
                                      .withId(doc.getDocument().getId());

                              announcementList.add(annonPost);
                              announcementRecyclerAdapter.notifyDataSetChanged();
                              noContent.setVisibility(View.GONE);
                              label.setVisibility(View.VISIBLE);
                          }

                          //This will be called once a user added new item to database and put it to top of the list
                          else if (!isFirstListLoaded){

                              if (containsLocation(announcementList, doc.getDocument().getId() )){
                                  Log.d(TAG, "Items are gonna duplicate!");

                              }
                              else{
                                  //Call the model to populate it with document
                                  AnnouncementModel annonPost = doc.getDocument().toObject(AnnouncementModel.class)
                                          .withId(doc.getDocument().getId());

                                  //This will be called only if user added some new post
                                  announcementList.add(0, annonPost);
                                  announcementRecyclerAdapter.notifyItemInserted(0);
                                  announcementRecyclerAdapter.notifyItemRangeChanged(0, announcementList.size());
                              }
                          }

                          //Just checking of where's the data fetched from
                          String source = documentSnapshots.getMetadata().isFromCache() ?
                                  "Local" : "Server";

                          Log.d(TAG, "Data fetched from " + source + "\n" + doc.getDocument().getData());

                          break;
                  }

              }
              //After the first item/latest post was loaded set it to false it means that first items are already fetched
              isFirstListLoaded = false;
          }

      }
  });


delete_update_listener = mDatabase.collection("Announcements").addSnapshotListener(new EventListener<QuerySnapshot>() {
      @Override
      public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapshots, @javax.annotation.Nullable FirebaseFirestoreException e) {

          //If something went wrong
          if (e != null)
              Log.w(TAG, "Listen failed.", e);

          if (queryDocumentSnapshots != null && !queryDocumentSnapshots.isEmpty()) {
              //Instead of simply using the entire query snapshot
              //See the actual changes to query results between query snapshots (added, removed, and modified)
              for (DocumentChange doc : queryDocumentSnapshots.getDocumentChanges()) {
                  switch (doc.getType()) {
                      case MODIFIED:
                          Log.d(TAG, "Modified city: " + doc.getDocument().getData());
                          break;
                      case REMOVED:
                          //Get the document ID of post in FireStore
                          //Perform a loop and scan the list of announcement to target the correct index
                          for(int i = 0; i < announcementList.size(); i++) {
                              //Check if the deleted document ID is equal or exist in the list of announcement
                              if(doc.getDocument().getId().equals(announcementList.get(i).AnnouncementsID)) {
                                  int prevSize = announcementList.size();
                                  //If yes then delete that object in list by targeting its index
                                  Log.d(TAG, "Removed city: " + announcementList.get(i).getTitle());
                                  announcementList.remove(i);
                                  //Notify the adapter that some item gets remove
                                  announcementRecyclerAdapter.notifyItemRemoved(i);
                                  announcementRecyclerAdapter.notifyItemRangeChanged(i,prevSize-i);
                                  break;
                              }
                          }
                          break;
                  }
              }
          }

      }
  });



//Load more queries
    private void loadMoreList() {
        //Load the next item(s) to display
        //Set a query according to time in milliseconds
        //This time start getting data AFTER the last item(s) loaded
        if (mLastSeen != null)
            mQuery = mDatabase.collection("Announcements")
                    .orderBy("time", Query.Direction.DESCENDING)
                    .startAfter(mLastSeen)
                    .limit(5);

        //Getting all documents under Announcement collection with query's condition
        annon_listener = mQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(final QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
                //If something went wrong
                if (e != null)
                    Log.w(TAG, "Listen failed.", e);

                if (documentSnapshots != null && !documentSnapshots.isEmpty()) {
                    //If more data exist then update our 'mLastSeen' data
                    //Update the last list shown in our RecyclerView
                    mLastSeen = documentSnapshots.getDocuments().get(documentSnapshots.size() - 1);

                    //Loop to read each document
                    for (DocumentChange doc : documentSnapshots.getDocumentChanges()) {
                        //Only added document will be read
                        switch (doc.getType()) {

                            case ADDED:
                                //Call the model to repopulate it with document
                                AnnouncementModel annonPost = doc.getDocument().toObject(AnnouncementModel.class)
                                        .withId(doc.getDocument().getId());

                                int prevSize = announcementList.size();

                                //Add any new item(s) to the List
                                announcementList.add(annonPost);
                                //Update the Recycler adapter that new data is added
                                //This trick performs recycling even though we set nested scroll to false
                                announcementRecyclerAdapter.notifyItemInserted(prevSize);
                                announcementRecyclerAdapter.notifyItemRangeInserted(prevSize, 5);

                                //Just checking of where's the data fetched from
                                String source = documentSnapshots.getMetadata().isFromCache() ?
                                        "Local" : "Server";

                                Log.d(TAG, "Data LOADED from " + source + "\n" + doc.getDocument().getData());
                                break;

                            case REMOVED:
                                break;

                            case MODIFIED:

                                break;

                        }

                    }
                }

                //If no more item(s) to load
                else if (!isDetached() && getContext() != null)
                    StyleableToast.makeText(getContext(), "All items are loaded.", R.style.mytoastNoItems).show();

            }
        });
    }

此外,我试着观察doc类型如何工作“ADDED”,“REMOVED”和“MODIFIED”。如果我在使用查询的侦听器中也放置了“REMOVED”,则REMOVED是在添加新项目时首先被调用,然后是ADDED,这将导致更多麻烦。

2 个答案:

答案 0 :(得分:2)

所以最后几个月后,我找到了使用RecyclerView使用FireStore执行实时更新的最佳方法和最佳方法。在Alex Mamo的回答here

的帮助下

最好的方法是获取数据/文档一次,然后为该集合提供一个ListenerRegistration。这是我的解决方案。

首先是初始化成员布尔变量并将其设置为 true。 这是必要的,因为在第一次启动时会触发文档类型 ADDED ,而我们不需要。

private boolean isFirstListLoaded = true;

接下来是声明你的ListenerRegistration,这是一个可选的,但我强烈建议你提供一个监听器,这样你就不再需要在 addSnapshotListener 中包含'this'了 strong>参数。将'this'包含在参数中会为数据节省一些内存,但有时会停止实时功能,因为它依赖于碎片或活动生命周期,这会破坏实时更新的目的

 private ListenerRegistration update_listener

然后像这样创建你的查询

 private Query mQuery;

升序或降序和限制取决于你。

将它放在 onCreate 方法上,这样它只会运行一次。

mQuery= mDatabase.collection("Your Collection")
                .orderBy("some fields within each document like names or time", Query.Direction.DESCENDING)
                .limit(5);


  //Run the first query in the beginning
        mQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if (task.isSuccessful()) {
                    if (!task.getResult().isEmpty()) {

                        //Get the documents of last item listed in our RecyclerView
                        mLastSeen = task.getResult().getDocuments().get(task.getResult().size() - 1);

                        //Loop to read each document
                        for (DocumentSnapshot document : task.getResult()) {

                            //Call the model to populate it with document
                            Model model = Objects.requireNonNull(document.toObject(Model .class))
                                    .withId(document.getId());
                            //Add every item/document to the list
                            mList.add(model);
                            //Notify the adapter that new item is added
                            yourRecyclerAdapter.notifyItemInserted(mList.size());
                            noContent.setVisibility(View.GONE);
                            label.setVisibility(View.VISIBLE);

                            //Just checking of where's the data fetched from
                            String source = document.getMetadata().isFromCache() ?
                                    "Local" : "Server";

                            Log.d(TAG, "Data fetched from " + source + "\n" + document.getData());
                        }
                     }

                    //If task is successful even though there's no existing item yet, then first fetch is success
                isFirstListLoaded = false;
                }
                else if (getContext() != null)
                    Toast.makeText(getContext(),"Error: "+ Objects.requireNonNull(task.getException()).getMessage(),Toast.LENGTH_LONG).show();

            }
        });

也是这个。

 //Listener
    update_listener = mDatabase.collection("Announcements").addSnapshotListener(new EventListener<QuerySnapshot>() {
        @Override
        public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapshots, @javax.annotation.Nullable FirebaseFirestoreException e) {

            //If something went wrong
            if (e != null)
                Log.w(TAG, "Listen failed.", e);

            if (queryDocumentSnapshots != null) {
                //Instead of simply using the entire query snapshot
                //See the actual changes to query results between query snapshots (added, removed, and modified)
                for (DocumentChange doc : queryDocumentSnapshots.getDocumentChanges()) {
                    switch (doc.getType()) {

                        case ADDED:

                            if (!isFirstListLoaded){
                                //Call the model to populate it with document
                                Model model= doc.getDocument().toObject(Model.class)
                                        .withId(doc.getDocument().getId());

                                //This will be called only if user added some new post
                                mList.add(0, model);
                                yourRecyclerAdapter.notifyItemInserted(0);
                                yourRecyclerAdapter.notifyItemRangeChanged(0, announcementList.size());
                            }

                            break;

                        case MODIFIED:
                            break;

                        case REMOVED:
                            //Get the document ID of post in FireStore
                            //Perform a loop and scan the list of announcement to target the correct index
                            for (int i = 0; i < announcementList.size(); i++) {
                                //Check if the deleted document ID is equal or exist in the list of announcement
                                if (doc.getDocument().getId().equals(announcementList.get(i).AnnouncementsID)) {
                                    //If yes then delete that object in list by targeting its index
                                    Log.d(TAG, "Removed Post: " + announcementList.get(i).getTitle());
                                    announcementList.remove(i);
                                    //Notify the adapter that some item gets remove
                                    announcementRecyclerAdapter.notifyItemRemoved(i);
                                    announcementRecyclerAdapter.notifyItemRangeChanged(i, announcementList.size());
                                    break;
                                }
                            }
                            break;
                    }
                }

                isFirstListLoaded = false;
            }

        }
    });

然后,只要您想加载更多项目,请调用此方法。

  private void loadMoreList() {
    //Load the next item(s) to display
    //Set a query according to time in milliseconds
    //This time start getting data AFTER the last item(s) loaded
    if (mAnnonLastSeen != null)
        mAnnouncementQuery = mDatabase.collection("Your collection")
                .orderBy("some field within your documents", Query.Direction.DESCENDING)
                .startAfter(mLastSeen)
                .limit(5);

    mAnnouncementQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                if (!task.getResult().isEmpty()) {
                    //Get the documents of last item listed in our RecyclerView
                    mLastSeen = task.getResult().getDocuments().get(task.getResult().size() - 1);

                    //Loop to read each document
                    for (DocumentSnapshot document : task.getResult()) {

                        //Call the model to populate it with document
                        AnnouncementModel annonPost = Objects.requireNonNull(document.toObject(AnnouncementModel.class))
                                .withId(document.getId());

                        //Add any new item(s) to the List
                        announcementList.add(annonPost);
                        //Update the Recycler adapter that new data is added
                        //This trick performs recycling even though we set nested scroll to false
                        announcementRecyclerAdapter.notifyItemInserted(announcementList.size());

                        //Just checking of where's the data fetched from
                        String source = document.getMetadata().isFromCache() ?
                                "Local" : "Server";

                        Log.d(TAG, "Data fetched from " + source + "\n" + document.getData());
                    }

                } else if (!isDetached() && getContext() != null)
                    Toast.makeText(getContext(), "All items are loaded.", Toast.LENGTH_LONG).show();

            }
            else if (getContext() != null)
                Toast.makeText(getContext(),"Error: "+ Objects.requireNonNull(task.getException()).getMessage(), Toast.LENGTH_LONG).show();

        }
    });
}

其中mLastSeen是成员varialble DocumentSnapshot。干杯!

答案 1 :(得分:0)

您似乎正在从细分和每个细分中构建列表:

  • 从特定文件开始
  • 包含接下来的5个项目

在这种情况下,从细分中删除项目会导致对下一个细分的起始文档进行其他更改。

虽然它对服务器的负载不是太糟糕,但由于大多数文档都来自本地缓存,这确实导致了一些文件的混乱。

因此,您会发现许多开发人员采用其他方法。我看到的最常见的是只有一个段,只是在用户向下滚动时增加限制。因此查询最初有5个项目,然后是10项,然后是15项等。

更复杂的情况是将每个段锚定在起始文档和结束文档。这样,从段内删除文档,不会更改其周围的其他段。但是这种情况还有其他一些问题,所以我一定会先找一些比较有名的东西。