У меня есть приложение, в котором пользователь может разместить элемент, отображаемый в RecyclerView, где Cloud FireStore является бэкэндом.Теперь при первом запуске приложения оно загрузит первые 5 элементов.Если RecyclerView больше не может прокручивать по вертикали, он получит еще 5 элементов.Все работает, за исключением случаев, когда я удаляю один элемент, другой элемент дублируется.
Первый сценарий проблемы:
При первом запуске загружается 5 элементов, поэтому в моем списке только 5 элементов на данный момент,Так как мой предел нумерации страниц равен 5, когда я удаляю один элемент из этих 5 элементов, слушатели запросов пытаются загрузить 6-й элемент.В этой части, когда я прокручиваю вверх, чтобы загрузить следующие 5 элементов, я получу дубликат 6-го элемента.
Как это 1, 2, 3, 4, 5
, тогда третий элемент будет удален 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
.
Моя идея: нужно ли мне обновлять значение lastSeen для DocumentSnapshot в каждом действии удаления или мне нужно динамически корректировать значение 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();
}
});
}
Кроме того, я попытался наблюдать, как работают типы документов "ДОБАВЛЕНО", "УДАЛЕНО" и«МОДИФИЦИРОВАННЫЙ».Если я добавлю «REMOVED» также внутри слушателей, использующих запрос, то REMOVED - это тот, который вызывается первым, а затем «ADDED» при добавлении новых элементов, что вызовет больше проблем.