I have a RecyclerView that I fill with data using an AsyncTask. When I clear the List with clear()
and mAdapter.notifyDataSetChanged()
and then try to fill it again with the AsyncTask, I get this exception:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter position
Replacing notifyItemInserted()
with notifyDataSetChanged()
inside my AsyncTask solves the problem, but I don't think that's a good solution, and I'd like to understand why the first method doesn't work.
My AsyncTask doInBackground()
method:
@Override
protected Void doInBackground(Void... voids) {
for (int i = 0; i < 100; i++) {
mDataContainer.addItem(i);
publishProgress(i);
}
return null;
}
and my AsyncTask onProgressUpdate()
method:
@Override
protected void onProgressUpdate(Integer... values) {
mAdapter.notifyItemInserted(values[0]);
super.onProgressUpdate(values);
}
I hope someone can help me with this. Thanks in advance.
Edit: Here is the adapter:
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<Item> mItems;
private int selectedPos = RecyclerView.NO_POSITION;
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private Item mItem;
private TextView mNameTextView;
private TextView mMembersTextView;
MyViewHolder(View view) {
super(view);
itemView.setOnClickListener(this);
mNameTextView = itemView.findViewById(R.id.item_name);
mMembersTextView = itemView.findViewById(R.id.item_members);
}
@Override
public void onClick(View view) {
notifyItemChanged(selectedPos);
selectedPos = getLayoutPosition();
notifyItemChanged(selectedPos);
mOnItemSelectedListener.onItemSelected(mItem);
}
}
MyAdapter(List<Item> items) {
mItems = items;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(getActivity())
.inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, int position) {
Item item = mItems.get(position);
myViewHolder.mItem = item;
myView.mNameTextView.setText(item.getName());
myView.mMembersTextView.setText(String.format(Locale.US,"%d/50", item.getMembers()));
if (!mIsInit) {
// select item that was selected before orientation change
if (selectedPos != RecyclerView.NO_POSITION) {
Item selectedItem = mItems.get(selectedPos);
mOnItemSelectedListener.onItemSelected(selectedItem);
// else select item 0 as default on landscape mode
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
selectedPos = 0;
mOnItemSelectedListener.onItemSelected(MyViewHolder.mItem);
}
mIsInit = true;
}
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
myViewHolder.itemView.setSelected(selectedPos == position);
}
}
@Override
public int getItemCount() {
return mItems.size();
}
}
You should only be adding items to the adapter (and notifying) from the UI thread. Changing the adapter's backing data from the background and then notifying on the UI thread later via onProgressUpdate
is quite likely to result in race conditions. In this case, mDataContainer.addItem(i);
should be moved to onProgressUpdate
before you notify the adapter.
It's hard to confirm if that's the only issue without seeing the definition of mDataContainer
, but fixing the synchronization here would definitely be the first step toward fixing this.