0

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

Nick Nelson
  • 1,131
  • 2
  • 18
  • 36
  • 1
    Did you every find the solution here? I might be running into a similar issue. – Bink Dec 31 '18 at 18:57
  • My only solution so far has been to force areContentsTheSame() to return false because I know I am only calling submitList() when something has changed. I still think this is odd behavior and not working properly. When I actually compare the 2 items, it is still returning true. – Nick Nelson Jan 01 '19 at 19:25

1 Answers1

1

I meet the same program with you, thanks to this answer, I finally find that to use the Paging Library, you need to make sure that the PagedList and it's item BOTH a new object from the old one.

There is my solution:

 if (thisWord.getIsSelected()) {
     thisWord.setIsSelected(false);
 } else {
     thisWord.setIsSelected(true);
 }

WordEntity newWord =  copyNewObject(thisWord);
mWordViewModel.update(newWord);
iDeveloper
  • 31
  • 3