I have added a checkbox and the paging library to Google's RoomWithAView codelab and the call to DiffUtil.ItemCallback seems to be passing the updated version of the entity to both the oldItem and newItem parameters.
My checkbox checked state is driven by a boolean field in the database named "isSelected" which gets updated when the row is clicked and this should cause the checkbox state to change.
The problem is that when I update the "isSelected" field (from false to true for example), the following Log printing returns true for both items. My checkbox state doesn't change because areContentsTheSame returns true and onBindViewHolder isn't called. I can force this to return false, but I want to understand what is going wrong:
private static DiffUtil.ItemCallback<WordEntity> DIFF_CALLBACK =
new DiffUtil.ItemCallback<WordEntity>() {
@Override
public boolean areItemsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","areItemsTheSame: " +
Boolean.toString(oldItem.getWordId()==newItem.getWordId()));
return oldItem.getWordId() == newItem.getWordId();
}
@Override
public boolean areContentsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","oldItem: " +
Boolean.toString(oldItem.getIsSelected()));
Log.i("CLEAN_LOG","newItem: " +
Boolean.toString(newItem.getIsSelected()));
Log.i("CLEAN_LOG","areContentsTheSame: " +
Boolean.toString(oldItem.getIsSelected() == newItem.getIsSelected()));
return oldItem.getIsSelected() == newItem.getIsSelected();
}
};
Here is my PagedListAdapter:
public static class WordListAdapter extends PagedListAdapter<WordEntity, WordListAdapter.WordViewHolder> {
protected WordListAdapter() {
super(DIFF_CALLBACK);
setHasStableIds(true);
}
@NonNull
@Override
public WordViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_item, parent, false);
return new WordViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull WordViewHolder holder, int position) {
WordEntity current = getItem(position);
if (current != null) {
holder.bindTo(current);
}
}
private static DiffUtil.ItemCallback<WordEntity> DIFF_CALLBACK =
new DiffUtil.ItemCallback<WordEntity>() {
@Override
public boolean areItemsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","areItemsTheSame: " +
Boolean.toString(oldItem.getWordId()==newItem.getWordId()));
return oldItem.getWordId() == newItem.getWordId();
}
@Override
public boolean areContentsTheSame(WordEntity oldItem, WordEntity newItem) {
Log.i("CLEAN_LOG","oldItem: " +
Boolean.toString(oldItem.getIsSelected()));
Log.i("CLEAN_LOG","newItem: " +
Boolean.toString(newItem.getIsSelected()));
Log.i("CLEAN_LOG","areContentsTheSame: " +
Boolean.toString(oldItem.getIsSelected() == newItem.getIsSelected()));
return oldItem.getIsSelected() == newItem.getIsSelected();
}
};
@Override
public long getItemId(int position) {
WordEntity current = getItem(position);
return current.mWordId;
}
class WordViewHolder extends RecyclerView.ViewHolder {
TextView wordItemView;
CheckBox checkBox;
LinearLayout viewForeground;
public void bindTo(WordEntity word) {
wordItemView.setText(word.mWord);
checkBox.setChecked(word.mIsSelected);
}
private WordViewHolder(View itemView) {
super(itemView);
viewForeground = itemView.findViewById(R.id.viewForeground);
wordItemView = itemView.findViewById(R.id.textView);
checkBox = itemView.findViewById(R.id.checkBox);
checkBox.setClickable(false);
viewForeground.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
final WordEntity thisWord = getItem(getAdapterPosition());
if (thisWord != null) {
Toast.makeText(context,
"You long-clicked: " + thisWord.getWord(),
Toast.LENGTH_LONG).show();
}
// returning false here will alow onClickListener to trigger as well
return true;
}
});
viewForeground.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final WordEntity thisWord = getItem(getAdapterPosition());
if (thisWord != null) {
if (thisWord.getIsSelected()) {
thisWord.setIsSelected(false);
} else {
thisWord.setIsSelected(true);
}
mWordViewModel.update(thisWord);
}
}
});
}
}
}
Here is my observer:
mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);
mWordViewModel.getAllWords().observe(this, new Observer<PagedList<WordEntity>>() {
@Override
public void onChanged(@Nullable final PagedList<WordEntity> words) {
// Update the cached copy of the words in the adapter.
adapter.submitList(words);
if (words != null) {
wordCount = words.size();
} else {
wordCount = 0;
}
Log.i(LOG_TAG,"Word Count: " + Integer.toString(wordCount));
}
});
- All the Room database updates are happening properly
- Based on the Log, it seems that areItemsTheSame is getting called twice when a row is tapped and areContentsTheSame is getting called once
I was expecting oldItem.getIsSelected() to be false and new.Item.getIsSelected() to be true and then onBindViewHolder would be fired. I also expected areItemsTheSame and areContentsTheSame to only get called once for each item.
Can someone help me understand what is going wrong here and if my expectations are in line with what should be happening?
Here is a GitHub with my sample app: https://github.com/DanglaGT/RoomWithAViewPaging