RecycleView 在记录删除后损坏。

huangapple go评论70阅读模式
英文:

RecycleView gets corrupted after record delete

问题

以下是您提供的代码的中文翻译:

我有一个[Android RecyclerView][1]通过[RecyclerView.Adapter][2][ArrayList][3]中的[Pair][4]以及`PlantRecord`显示一些位置

    package com.mikroelektronika.guestnotificationsystem.Database;
    
    import androidx.annotation.NonNull;
    
    import java.io.Serializable;
    import java.util.Comparator;
    
    public class PlantRecord implements Serializable
    {
        private static final String TAG=PlantRecord.class.getSimpleName();
    
        public static final String TABLE_ID="plants";
    
        public static final String FIELD_ID="id";
        public static final String FIELD_NAME="name";
        public static final String FIELD_PHOTO_URL="photo_url";
        public static final String FIELD_STREET="street";
        public static final String FIELD_STREET_NR="street_nr";
        public static final String FIELD_ZIPCODE="zip_code";
        public static final String FIELD_CITY="city";
        public static final String FIELD_COUNTRY="country";
    
        public static final String INVALID_FIELD_ID_VALUE="";
        public static final String INVALID_FIELD_NAME_VALUE="";
        public static final String INVALID_FIELD_PHOTO_URL_VALUE="";
        public static final String INVALID_FIELD_STREET_VALUE="";
        public static final String INVALID_FIELD_STREET_NR_VALUE="";
        public static final String INVALID_FIELD_ZIPCODE_VALUE="";
        public static final String INVALID_FIELD_CITY_VALUE="";
        public static final String INVALID_FIELD_COUNTRY_VALUE="";
    
        public String id;
        public String name;
        public String photo_url;
        public String street;
        public String street_nr;
        public String zip_code;
        public String city;
        public String country;
    
        public PlantRecord()
        {
            this.id=INVALID_FIELD_ID_VALUE;
            this.name=INVALID_FIELD_NAME_VALUE;
            this.photo_url=INVALID_FIELD_PHOTO_URL_VALUE;
            this.street=INVALID_FIELD_STREET_VALUE;
            this.street_nr=INVALID_FIELD_STREET_NR_VALUE;
            this.zip_code=INVALID_FIELD_ZIPCODE_VALUE;
            this.city=INVALID_FIELD_CITY_VALUE;
            this.country=INVALID_FIELD_COUNTRY_VALUE;
        }
    
        public PlantRecord(final String id,
            final String name,
            final String photo_url,
            final String street,
            final String street_nr,
            final String zip_code,
            final String city,
            final String country)
        {
            this.id=id;
            this.name=name;
            this.photo_url=photo_url;
            this.street=street;
            this.street_nr=street_nr;
            this.zip_code=zip_code;
            this.city=city;
            this.country=country;
        }
    
        public static Comparator<PlantRecord> plantRecordComparatorByPlantNameAscending=Comparator.comparing(firstPlantRecord->
                                                                                                                 firstPlantRecord.name);
    
        public static Comparator<PlantRecord> plantRecordComparatorByPlantNameDescending=(firstPlantRecord, secondPlantRecord)->secondPlantRecord.name
            .compareTo(firstPlantRecord.name);
    
        @NonNull
        @Override
        public String toString()
        {
            return "id:"+
                   this.id+
                   " "+
                   "name:"+
                   this.name+
                   " "+
                   "photo_url:"+
                   this.photo_url+
                   " "+
                   "street:"+
                   this.street+
                   " "+
                   "street_nr:"+
                   this.street_nr+
                   " "+
                   "zip_code:"+
                   this.zip_code+
                   " "+
                   "city:"+
                   this.city+
                   " "+
                   "country:"+
                   this.country;
        }
    }

作为数据源

    package com.mikroelektronika.guestnotificationsystem.PlantsSelector;
    
    import android.util.Pair;
    
    import com.mikroelektronika.guestnotificationsystem.Database.PlantRecord;
    
    import java.util.ArrayList;
    
    /**
     * @brief 数据源数据结构
     * @param first - 用于选择记录(多选)的布尔值 true - 选择 false - 未选择
     * @param second - 植物记录
     */
    public class PlantsDataSource extends ArrayList<Pair<Boolean, PlantRecord>>
    {
    }

如果数据源在启动时正确填充并且`RecyclerView`也来自[Google Firebase][6]正确显示

[![来自Google Firebase的数据显示的RecyclerView][7]][7]

现在如果需要可以选择项目然后通过以下删除过程的[ImageButton][8]进行删除

```java
this.btnDeletePlant.setOnClickListener(v -> {

	if (selectedPlantsItems.size() > 0) {

		for (Pair<Integer, PlantRecord> selectedPlantItem : selectedPlantsItems) {
			DatabaseReference removingPlantRecordRef =
					FirebaseDatabase
							.getInstance()
							.getReference()
							.child(PlantRecord.TABLE_ID)
							.child(CurrentLoggedUser.getUserId())
							.child(selectedPlantItem.second.id);

			Log.i(TAG, "selectedPlantItem.second.id:" + selectedPlantItem.second.id);

			removingPlantRecordRef.removeValue()

					.addOnCanceledListener(() -> Log.i(TAG, "Remove canceled."))

					.addOnCompleteListener(task -> {
						if (task.isSuccessful()) {
							Log.i(TAG, "Remove completed.");
						}
					})

					.addOnFailureListener(e -> Log.e(TAG, "Remove failed with error:" + e))

					.addOnSuccessListener(unused -> {
						Log.i(TAG, "Remove successfull, selected plant item index:" + selectedPlantItem.first);
						plantsSelectionFragmentAdapter.removeItem(selectedPlantItem.first);
					});
		}
	}
	
});

我们还可以在Android Studio Logcat中看到项目已删除并且没有错误:

2023-04-10 13:04:02.339 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  selectedPlantItem.second.id:-NSef13zeWQL1Sim-iPo
2023-04-10 13:04:02.404 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  Remove completed.
2023-04-10 13:04:02.404 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  Remove successfull, selected plant item 

RecycleView 在记录删除后损坏。

我已经检查了50多次,正确的项目已从Firebase中删除,但RecyclerView出现了问题:

RecycleView 在记录删除后损坏。

为什么会发生这种情况,我不知道如何修复?

附录 1

@dominicoder 这是removeItem(final int itemIndex)的代码:

public void removeItem(final int itemIndex)
{
    this.plantsDataSource
        .remove(itemIndex);
    
    notifyItemRemoved(itemIndex);
    this.selectedItemsCounter
        .decrease();
}

它从数据源中删除项目并通知适配器进行删除,最后还减少了所选项目的计数。但是,我已经测试了具有和没有这个方法

英文:

I have Android RecyclerView showing some locations via RecyclerView.Adapter and ArrayList of Pair of Boolean and PlantRecord:

package com.mikroelektronika.guestnotificationsystem.Database;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Comparator;
public class PlantRecord implements Serializable
{
private static final String TAG=PlantRecord.class.getSimpleName();
public static final String TABLE_ID=&quot;plants&quot;;
public static final String FIELD_ID=&quot;id&quot;;
public static final String FIELD_NAME=&quot;name&quot;;
public static final String FIELD_PHOTO_URL=&quot;photo_url&quot;;
public static final String FIELD_STREET=&quot;street&quot;;
public static final String FIELD_STREET_NR=&quot;street_nr&quot;;
public static final String FIELD_ZIPCODE=&quot;zip_code&quot;;
public static final String FIELD_CITY=&quot;city&quot;;
public static final String FIELD_COUNTRY=&quot;country&quot;;
public static final String INVALID_FIELD_ID_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_NAME_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_PHOTO_URL_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_STREET_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_STREET_NR_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_ZIPCODE_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_CITY_VALUE=&quot;&quot;;
public static final String INVALID_FIELD_COUNTRY_VALUE=&quot;&quot;;
public String id;
public String name;
public String photo_url;
public String street;
public String street_nr;
public String zip_code;
public String city;
public String country;
public PlantRecord()
{
this.id=INVALID_FIELD_ID_VALUE;
this.name=INVALID_FIELD_NAME_VALUE;
this.photo_url=INVALID_FIELD_PHOTO_URL_VALUE;
this.street=INVALID_FIELD_STREET_VALUE;
this.street_nr=INVALID_FIELD_STREET_NR_VALUE;
this.zip_code=INVALID_FIELD_ZIPCODE_VALUE;
this.city=INVALID_FIELD_CITY_VALUE;
this.country=INVALID_FIELD_COUNTRY_VALUE;
}
public PlantRecord(final String id,
final String name,
final String photo_url,
final String street,
final String street_nr,
final String zip_code,
final String city,
final String country)
{
this.id=id;
this.name=name;
this.photo_url=photo_url;
this.street=street;
this.street_nr=street_nr;
this.zip_code=zip_code;
this.city=city;
this.country=country;
}
public static Comparator&lt;PlantRecord&gt; plantRecordComparatorByPlantNameAscending=Comparator.comparing(firstPlantRecord-&gt;
firstPlantRecord.name);
public static Comparator&lt;PlantRecord&gt; plantRecordComparatorByPlantNameDescending=(firstPlantRecord, secondPlantRecord)-&gt;secondPlantRecord.name
.compareTo(firstPlantRecord.name);
@NonNull
@Override
public String toString()
{
return &quot;id:&quot;+
this.id+
&quot; &quot;+
&quot;name:&quot;+
this.name+
&quot; &quot;+
&quot;photo_url:&quot;+
this.photo_url+
&quot; &quot;+
&quot;street:&quot;+
this.street+
&quot; &quot;+
&quot;street_nr:&quot;+
this.street_nr+
&quot; &quot;+
&quot;zip_code:&quot;+
this.zip_code+
&quot; &quot;+
&quot;city:&quot;+
this.city+
&quot; &quot;+
&quot;country:&quot;+
this.country;
}
}

as datasource:

package com.mikroelektronika.guestnotificationsystem.PlantsSelector;
import android.util.Pair;
import com.mikroelektronika.guestnotificationsystem.Database.PlantRecord;
import java.util.ArrayList;
/**
* @brief Data source data structure
* @param first - Boolean for selected record (multiselection) true - selected false - unselected
* @param second - Plant record
*/
public class PlantsDataSource extends ArrayList&lt;Pair&lt;Boolean, PlantRecord&gt;&gt;
{
}

Datasource if populated correctly at startup and RecyclerView is also filled and shown correctly from Google Firebase:
RecycleView 在记录删除后损坏。
Now, if needed, item is selected and then deleted via ImageButton with following deletion process code:

this.btnDeletePlant.setOnClickListener(v -&gt; {
if (selectedPlantsItems.size() &gt; 0) {
for (Pair&lt;Integer, PlantRecord&gt; selectedPlantItem : selectedPlantsItems) {
DatabaseReference removingPlantRecordRef =
FirebaseDatabase
.getInstance()
.getReference()
.child(PlantRecord.TABLE_ID)
.child(CurrentLoggedUser.getUserId())
.child(selectedPlantItem.second.id);
Log.i(TAG, &quot;selectedPlantItem.second.id:&quot; + selectedPlantItem.second.id);
removingPlantRecordRef.removeValue()
.addOnCanceledListener(() -&gt; Log.i(TAG, &quot;Remove canceled.&quot;))
.addOnCompleteListener(task -&gt; {
if (task.isSuccessful()) {
Log.i(TAG, &quot;Remove completed.&quot;);
}
})
.addOnFailureListener(e -&gt; Log.e(TAG, &quot;Remove failed with error:&quot; + e))
.addOnSuccessListener(unused -&gt; {
Log.i(TAG, &quot;Remove successfull, selected plant item index:&quot; + selectedPlantItem.first);
plantsSelectionFragmentAdapter.removeItem(selectedPlantItem.first);
});
}
}
});

We can also see in the Android Studio Logcat item is deleted without errors:

2023-04-10 13:04:02.339 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  selectedPlantItem.second.id:-NSef13zeWQL1Sim-iPo
2023-04-10 13:04:02.404 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  Remove completed.
2023-04-10 13:04:02.404 16640-16640 PlantSelectionFragment  com.mikroelektronika                 I  Remove successfull, selected plant item 

RecycleView 在记录删除后损坏。

I've checked 50 or more times the right item is removed from Firebase and indeed it is. However, RecyclerView gets corrupted as seen in tthe following screen record (converted to animated gif):
RecycleView 在记录删除后损坏。
Why is this happening, I have no idea how to fix this?

ADDENDUM 1

@dominicoder here is removeItem(final int itemIndex) code:

public void removeItem(final int itemIndex)
{
this.plantsDataSource
.remove(itemIndex);
notifyItemRemoved(itemIndex);
this.selectedItemsCounter
.decrease();
}

It removes item from data source and notifies adapter about removal at the end it also decreases selected items counter. However, I've tested item removal with and without this method, result is same. Error must be somewhere in the item removal from database ...

ADDENDUM 2

If I disable Firebase saving code and just call removeItem in delete item button onClickListener, item is correctly removed from RecyclerView, will check removingPlantRecordRef ...

答案1

得分: 1

Disclaimer

这是一个指令性的回答,而不是对问题的直接回答。这样可以提供一些线索来解决 OP 的问题。

所以,你有一个循环遍历 PlantRecord 项目列表,然后为每个项目(取消、成功、失败和完成)注册了 4 个监听器,所以如果你有 100 个项目的列表,总共注册了 400 个监听器。这是相当大的数字,可能会出现错误。

而不是这样,你可以在这 100 个子项的父节点上注册一个单一的 ChildEventListener

也就是,不要使用这个引用:

DatabaseReference removingPlantRecordRef =
FirebaseDatabase
.getInstance()
.getReference()
.child(PlantRecord.TABLE_ID)
.child(CurrentLoggedUser.getUserId())
.child(selectedPlantItem.second.id);

你可以使用父节点:

DatabaseReference parentRef =
FirebaseDatabase
.getInstance()
.getReference()
.child(PlantRecord.TABLE_ID)
.child(CurrentLoggedUser.getUserId());

然后注册 ChildEventListener,并从 DataSnapshot 回调参数中筛选列表;最终通知 RecyclerView 适配器。

类似这样:

ChildEventListener listener = new ChildEventListener() {
@Override
public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
String secondId = dataSnapshot.getValue(String.class);
// 现在你从 Firebase 中有了 "selectedPlantItem.second.id",使用它来筛选 "selectedPlantsItems" 以获取 selectedPlantItem,然后获取 "selectedPlantItem.first"
for (Pair<Integer, PlantRecord> selectedPlantItem : selectedPlantsItems) {
if (secondId == selectedPlantItem.second.id) {
plantsSelectionFragmentAdapter.removeItem(selectedPlantItem.first);
}
}
}
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {}
};
parentRef.addChildEventListener(listener);
英文:

> Disclaimer
>
> This is a directive answer rather than a direct answer to the question. So that it could give some clue to solve the OP Question.

So, you have a loop that iterates over a list of PlantRecord items, then 4 listeners are registered for each (Cancellation, Success, Failure, and Completion), So, if you have a list of 100 items; 400 listeners are registered overall. This is fairly big number and can be error-prone.

Instead of that you can register a single ChildEventListener to the parent node of those 100 children/items.

i.e., instead of using this reference:

DatabaseReference removingPlantRecordRef =
FirebaseDatabase
.getInstance()
.getReference()
.child(PlantRecord.TABLE_ID)
.child(CurrentLoggedUser.getUserId())
.child(selectedPlantItem.second.id);

You'd use the parent:

DatabaseReference parentRef =
FirebaseDatabase
.getInstance()
.getReference()
.child(PlantRecord.TABLE_ID)
.child(CurrentLoggedUser.getUserId());

And register the ChildEventListener, and filter the list from the DataSnapshot callback parameter; and eventually notify the RecyclerView adapter.

Something like:

ChildEventListener listener = new ChildEventListener() {
@Override
public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
String secondId = dataSnapshot.getValue(String.class); 
// Now you&#39;ve &quot;selectedPlantItem.second.id&quot; from Firebase, use it to 
// filter the &quot;selectedPlantsItems&quot; to get the selectedPlantItem, 
// then get &quot;selectedPlantItem.first&quot;
for (Pair&lt;Integer, PlantRecord&gt; selectedPlantItem : selectedPlantsItems) {
if (secondId == selectedPlantItem.second.id) {
plantsSelectionFragmentAdapter.removeItem(selectedPlantItem.first);
}
}
}
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {}
};
parentRef.addChildEventListener(listener);

huangapple
  • 本文由 发表于 2023年4月10日 19:19:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75976614.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定