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()
的返回值更改其卡的颜色来显示在屏幕上。