3

我在DiffUtil.ItemCallback库中遇到问题,其中列表项的旧状态以某种方式最终丢失并导致oldTask具有与newTask.

当我选中/取消选中 ListAdapter 中的复选框并onChanged()在 LiveData 观察者中调用DiffUtil.ItemCallback函数时,就会发生这种情况。正如您在里面看到onBindViewHolder()的那样,该动作调用setCompleted()否定isCompleted(). 过期任务(红色)的预期行为是在选中复选框时变为白色。这是因为isOverdue()return 时只能返回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);
            }
        }
    });

}

但是,当areContentsTheSame()尝试比较旧列表和新列表中的值时,它有时会true在不应该返回的时候返回,因为isCompleted()应该为新旧列表项返回不同的值。似乎列表项的旧状态不知何故丢失了。这会导致列表项被错误地呈现,因为onBindViewHolder()没有被调用。这是一个 GIF,显示了应用程序的行为方式。如您所见,即使选中,列表项也是红色的,即使未选中,列表项也是白色的。您还可以看到一些列表项正确呈现,但不是全部。

差异回调:

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;
            }
        }
    };
}
}

这是上面链接的 GIF 中发生的部分日志:

2020-02-02 15:45:21.536 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onCheckedChanged: called for subTask Wash car isChecked = true
2020-02-02 15:45:21.555 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:21.555 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:21.556 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:21.556 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:21.556 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:21.556 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:21.557 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:21.557 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:21.557 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:21.557 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = true, completed = false]
2020-02-02 15:45:21.558 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = true, completed = false]
2020-02-02 15:45:21.558 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:21.558 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:21.558 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:21.558 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:22.979 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onCheckedChanged: called for subTask Read OS book isChecked = true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:22.999 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:23.000 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:23.001 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:23.001 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:23.002 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = true, completed = false]
2020-02-02 15:45:23.002 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = false, completed = true]
2020-02-02 15:45:23.002 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = false
2020-02-02 15:45:23.003 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:23.004 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:23.004 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:23.004 8226-8226/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: getChangePayload = false
2020-02-02 15:45:23.014 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onBindViewHolder: CALLED
2020-02-02 15:45:24.275 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onCheckedChanged: called for subTask Read database book isChecked = true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:24.294 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:24.295 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:24.295 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:24.295 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:24.296 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = false, completed = true]
2020-02-02 15:45:24.296 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = false, completed = true]
2020-02-02 15:45:24.296 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:24.296 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:24.297 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = false, completed = true]
2020-02-02 15:45:24.297 8226-11254/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = false
2020-02-02 15:45:24.297 8226-8226/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: getChangePayload = false
2020-02-02 15:45:24.308 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onBindViewHolder: CALLED
2020-02-02 15:45:26.590 8226-8226/com.johnsorhannus.divideandconquer D/ViewSTForMTAdapter: onCheckedChanged: called for subTask Read database book isChecked = false
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read database book Read database book true
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Read OS book Read OS book true
2020-02-02 15:45:26.608 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areItemsTheSame: Wash car Wash car true
2020-02-02 15:45:26.609 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:26.609 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Wash car, dueDate = Mar 17, 2019, overdue = false, completed = true]
2020-02-02 15:45:26.609 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:26.610 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = false, completed = true]
2020-02-02 15:45:26.610 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read OS book, dueDate = Mar 20, 2019, overdue = false, completed = true]
2020-02-02 15:45:26.610 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true
2020-02-02 15:45:26.611 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: oldTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:26.611 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame: newTask SubTask[name = Read database book, dueDate = Mar 23, 2019, overdue = true, completed = false]
2020-02-02 15:45:26.611 8226-11255/com.johnsorhannus.divideandconquer D/SubTaskDiffCallback: areContentsTheSame = true

我确定setCompleted()更改了 的值,isCompleted()因为当您退出并重新进入活动时,它会正确呈现

4

1 回答 1

4

DiffUtil通过引用存储旧项目 - 它不会复制它们。您的SubTask类是可变的,因此当您调用 时setCompleted,您正在更改 DiffUtil 引用的旧类。 DiffUtil只能与旧的、未更改的对象列表进行比较。因为您更改了旧对象,并且它只有对您现在更改的对象的引用,所以它不知道正确的旧值。

于 2020-02-03T19:11:30.767 回答