Java DiffUtil.ItemCallback有时会丢失oldItem,并导致areContentsTheSame()错误地返回true
我在Java DiffUtil.ItemCallback有时会丢失oldItem,并导致areContentsTheSame()错误地返回true,java,android,android-livedata,listadapter,android-diffutils,Java,Android,Android Livedata,Listadapter,Android Diffutils,我在DiffUtil.ItemCallback库中遇到了一个问题,其中列表项的旧状态以某种方式丢失,并导致oldTask与newTask具有相同的值 当我选中/取消选中ListAdapter中的复选框,并且LiveData observer中的onChanged()调用DiffUtil.ItemCallback函数时,就会发生这种情况。正如您在onBindViewHolder()中所看到的,该操作调用setCompleted(),这将否定isCompleted()的值。过期任务(红色)的预期行为
DiffUtil.ItemCallback
库中遇到了一个问题,其中列表项的旧状态以某种方式丢失,并导致oldTask
与newTask
具有相同的值
当我选中/取消选中ListAdapter中的复选框,并且LiveData observer中的onChanged()
调用DiffUtil.ItemCallback
函数时,就会发生这种情况。正如您在onBindViewHolder()
中所看到的,该操作调用setCompleted()
,这将否定isCompleted()
的值。过期任务(红色)的预期行为是每当选中其复选框时变为白色。这是因为isOverdue()
只能在isCompleted()时返回false
返回true
onBindViewHolder():
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
SubTask subTask = getItem(i);
Log.d(TAG, "onBindViewHolder: CALLED");
viewHolder.subTaskName.setText(subTask.getName());
Calendar dueDate = subTask.getDueDate();
viewHolder.dueDate.setText(context.getResources().getString(R.string.due_date, dueDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()), dueDate.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()), dueDate.get(Calendar.DAY_OF_MONTH), dueDate.get(Calendar.YEAR)));
viewHolder.checkBox.setOnCheckedChangeListener(null);
//check of sub task if completed
if (subTask.isCompleted()) {
viewHolder.checkBox.setChecked(true);
} else {
viewHolder.checkBox.setChecked(false);
}
if (subTask.isOverdue()) {
viewHolder.card.setCardBackgroundColor(context.getColor(R.color.red));
viewHolder.subTaskName.setTextColor(context.getColor(R.color.colorAccent));
viewHolder.dueDate.setTextColor(context.getColor(R.color.colorAccent));
} else {
viewHolder.card.setCardBackgroundColor(context.getColor(R.color.colorAccent));
viewHolder.subTaskName.setTextAppearance(R.style.TextAppearance_AppCompat_Large);
viewHolder.dueDate.setTextColor(context.getColor(R.color.main_task_text_color));
}
viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheckedChanged: called for subTask " + subTask.getName() + " isChecked = " + isChecked);
viewModel = ViewModelProviders.of((FragmentActivity) context).get(ViewSubTasksForMainTaskViewModel.class);
if (isChecked) {
//if overdue, change card to white
subTask.setCompleted(true);
viewModel.updateSubTask(subTask);
} else {
//if overdue, set color back to red
subTask.setCompleted(false);
viewModel.updateSubTask(subTask);
}
}
});
}
public class SubTaskDiffCallback {
private static final String TAG = "SubTaskDiffCallback";
static public DiffUtil.ItemCallback<SubTask> getSubTaskDiffCallback() {
return new DiffUtil.ItemCallback<SubTask>() {
@Override
public boolean areItemsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
Log.d(TAG, "areItemsTheSame: " + oldTask.getName() + " " + newTask.getName() + " " + Boolean.toString(oldTask.getId() == newTask.getId()));
return oldTask.getId() == newTask.getId();
}
@Override
public boolean areContentsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
Log.d(TAG, "areContentsTheSame: oldTask " + oldTask.toString());
Log.d(TAG, "areContentsTheSame: newTask " + newTask.toString());
boolean contentsSame = oldTask.getName().equals(newTask.getName()) &&
oldTask.getDueDate().equals(newTask.getDueDate()) &&
oldTask.isCompleted() == newTask.isCompleted() &&
oldTask.getMainTaskId() == (newTask.getMainTaskId());
Log.d(TAG, "areContentsTheSame = " + contentsSame);
return contentsSame;
}
@Nullable
@Override
public Object getChangePayload(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
if (oldTask.isCompleted() != newTask.isCompleted()) {
Log.d(TAG, "getChangePayload = false");
return Boolean.FALSE;
} else {
return null;
}
}
};
}
}
但是,当areContentsTheSame()
尝试比较旧列表和新列表中的值时,它有时会返回true
,而不应该返回,因为isCompleted()
应该为旧列表项和新列表项返回不同的值。似乎列表项的旧状态不知何故丢失了。这会导致列表项呈现不正确,因为未调用onBindViewHolder()
。这显示了应用程序的运行方式。如您所见,列表项即使已选中也为红色,而列表项即使未选中也为白色。您还可以看到一些列表项正确呈现,但并非所有列表项都正确呈现
DiffCallback:
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
SubTask subTask = getItem(i);
Log.d(TAG, "onBindViewHolder: CALLED");
viewHolder.subTaskName.setText(subTask.getName());
Calendar dueDate = subTask.getDueDate();
viewHolder.dueDate.setText(context.getResources().getString(R.string.due_date, dueDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()), dueDate.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()), dueDate.get(Calendar.DAY_OF_MONTH), dueDate.get(Calendar.YEAR)));
viewHolder.checkBox.setOnCheckedChangeListener(null);
//check of sub task if completed
if (subTask.isCompleted()) {
viewHolder.checkBox.setChecked(true);
} else {
viewHolder.checkBox.setChecked(false);
}
if (subTask.isOverdue()) {
viewHolder.card.setCardBackgroundColor(context.getColor(R.color.red));
viewHolder.subTaskName.setTextColor(context.getColor(R.color.colorAccent));
viewHolder.dueDate.setTextColor(context.getColor(R.color.colorAccent));
} else {
viewHolder.card.setCardBackgroundColor(context.getColor(R.color.colorAccent));
viewHolder.subTaskName.setTextAppearance(R.style.TextAppearance_AppCompat_Large);
viewHolder.dueDate.setTextColor(context.getColor(R.color.main_task_text_color));
}
viewHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onCheckedChanged: called for subTask " + subTask.getName() + " isChecked = " + isChecked);
viewModel = ViewModelProviders.of((FragmentActivity) context).get(ViewSubTasksForMainTaskViewModel.class);
if (isChecked) {
//if overdue, change card to white
subTask.setCompleted(true);
viewModel.updateSubTask(subTask);
} else {
//if overdue, set color back to red
subTask.setCompleted(false);
viewModel.updateSubTask(subTask);
}
}
});
}
public class SubTaskDiffCallback {
private static final String TAG = "SubTaskDiffCallback";
static public DiffUtil.ItemCallback<SubTask> getSubTaskDiffCallback() {
return new DiffUtil.ItemCallback<SubTask>() {
@Override
public boolean areItemsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
Log.d(TAG, "areItemsTheSame: " + oldTask.getName() + " " + newTask.getName() + " " + Boolean.toString(oldTask.getId() == newTask.getId()));
return oldTask.getId() == newTask.getId();
}
@Override
public boolean areContentsTheSame(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
Log.d(TAG, "areContentsTheSame: oldTask " + oldTask.toString());
Log.d(TAG, "areContentsTheSame: newTask " + newTask.toString());
boolean contentsSame = oldTask.getName().equals(newTask.getName()) &&
oldTask.getDueDate().equals(newTask.getDueDate()) &&
oldTask.isCompleted() == newTask.isCompleted() &&
oldTask.getMainTaskId() == (newTask.getMainTaskId());
Log.d(TAG, "areContentsTheSame = " + contentsSame);
return contentsSame;
}
@Nullable
@Override
public Object getChangePayload(@NonNull SubTask oldTask, @NonNull SubTask newTask) {
if (oldTask.isCompleted() != newTask.isCompleted()) {
Log.d(TAG, "getChangePayload = false");
return Boolean.FALSE;
} else {
return null;
}
}
};
}
}
我确信
setCompleted()
更改了isCompleted()
的值,因为当您退出并重新输入活动时,.DiffUtil
通过引用存储旧项目-它不会复制它们。您的子任务
类是可变的,因此当您调用setCompleted
时,您也在更改DiffUtil引用的旧任务DiffUtil
只能与旧的、未更改的对象列表进行比较。因为您更改了旧对象,而它只引用了您现在更改的对象,所以它不知道正确的旧值。尝试用onclick listener替换选中的已更改的侦听器。checked changed listener的问题是,当您以编程方式调用setChecked
时,它会触发,并且由于您没有正确使用更改负载,所以每当重用ViewHolder时,您都会泄漏以前的侦听器。@Pawel我将checked changed listener替换为,但不幸的是,它没有解决问题。我明白了。这里使用DiffUtil.ItemCallback
的目的是在用户删除列表项时提供动画。我应该对我的实现进行哪些更改,以使其按我想要的方式运行<代码>子任务需要是可变的,因为其内容的更改需要反映在我的房间数据库中,并最终通过基于isOverdue()
的返回值更改其卡的颜色来显示在屏幕上。