I have a list which is coming from an API which I'm storing in db. Now I'm fetching list from db and showing it in recyclerview. Then removing all data one-by-one from recyclerview listing. This process is being repeated every 10 seconds using JobScheduler. While I'm scrolling, I'm getting this error. I've tried many solutions given in various SO posts like this but it didn't worked.
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionMessageViewHolder{c2276c4 position=31 id=-1, oldPos=-1, pLpos:-1 no parent} androidx.recyclerview.widget.RecyclerView{5223262 VFED..... .F...... 0,0-720,1120 #7f0900d4 app:id/recycler}, adapter:com.sam.testapp.MessageAdapter@4b57ff3, layout:androidx.recyclerview.widget.LinearLayoutManager@735b4b0, context:com.sam.testapp.MainActivity@e780e56
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageViewHolder> {
private List<Message> messageList;
public MessageAdapter(List<Message> messageList) {
this.messageList = messageList;
}
protected static class MessageViewHolder extends RecyclerView.ViewHolder {
private ConstraintLayout root;
private TextView umfiTXT, msgTXT;
MessageViewHolder(View view) {
super(view);
root = view.findViewById(R.id.root);
umfiTXT = view.findViewById(R.id.umfiTXT);
msgTXT = view.findViewById(R.id.msgTXT);
}
}
@NonNull
@Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MessageViewHolder((LayoutInflater.from(parent.getContext())).inflate(R.layout.row_msg_list, parent, false));
}
@Override
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
try {
holder.umfiTXT.setText("UMFI: " + messageList.get(position).getMessageUMFI());
holder.msgTXT.setText("Message: " + messageList.get(position).getMessageTxt());
}catch(Exception e) {
e.printStackTrace();
}
}
@Override
public int getItemCount() {
try {
if(messageList.isEmpty())
return 0;
else
return messageList.size();
}catch(Exception e) {
return 0;
}
}
private void clearData() {
this.messageList.clear();
notifyDataSetChanged();
}
public void setData(List<Message> data) {
clearData();
this.messageList.addAll(data);
notifyDataSetChanged();
}
}
public class MainActivity extends AppCompatActivity {
private ArrayList<Message> messageList;
private RecyclerView recycler;
private AppCompatTextView emptyTxt;
private MessageAdapter messageAdapter;
private SqliteDatabase database;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
messageList = new ArrayList<>();
recycler = findViewById(R.id.recycler);
emptyTxt = findViewById(R.id.emptyTxt);
database = new SqliteDatabase(MainActivity.this);
recycler.setLayoutManager(new LinearLayoutManager(this));
messageAdapter = new MessageAdapter(messageList);
recycler.setAdapter(messageAdapter);
recycler.setItemAnimator(null);
RxBus.subscribe((Consumer<Object>) o -> {
if (o instanceof RxEvent) {
RxEvent data = (RxEvent) o;
System.out.println("SAM: status: " + data.getStatus());
if (data.getStatus() == 1) {
fetchMessageList();
}
}
});
}
private void fetchMessageList(){
messageList.clear();
AndroidNetworking.get(Util.url)
.setPriority(Priority.IMMEDIATE)
.build()
.getAsJSONArray(new JSONArrayRequestListener() {
@Override
public void onResponse(JSONArray jsonArray) {
try{
System.out.println("SAM: fetchMessageList jsonArray: "+jsonArray);
ArrayList<Message> templist = new ArrayList<>();
for(int i=0; i<jsonArray.length(); i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
templist.add(new Message(jsonObject.getString("umfi"), jsonObject.getString("msg_to"), jsonObject.getString("msg_text")));
}
storeListinDB(templist);
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public void onError(ANError error) {
System.out.println("SAM: fetchMessageList onError: "+error.getErrorBody());
}
});
}
private void storeListinDB(ArrayList<Message> templist){
database.insertArrayData(templist);
showList();
}
private void showList(){
try{
//recycler.getRecycledViewPool().clear();
if(messageList.size()>0){
emptyTxt.setVisibility(View.GONE);
messageAdapter.notifyDataSetChanged();
}else{
emptyTxt.setVisibility(View.VISIBLE);
ArrayList<Message> templist = new ArrayList<>();
templist = database.fetchMessageList();
messageAdapter.setData(templist);
System.out.println("SAM: templist size: "+templist.size());
}
System.out.println("SAM: messageList size: "+messageList.size());
//removeAll();
}catch(Exception e){
e.printStackTrace();
}
}
}
You pass your messageList
to the adapter, so the messageList
in the adapter and in the activity are the same object references. Then, as I understand, somehow method fetchMessageList
is called, it's where the problem appears. You call .clear()
on your list and start an asynchronous operation to fetch a new list, to then synchronously post it to your adapter.
The thing is, after you have cleared your list, your adapter keeps the reference to an empty list now, without being notified about the changes. The adapter still "thinks" that your list is the same size as it was before, so when you scroll the RecyclerView
, it tries to call at least onBindViewHolder
for new appearing items. But, as the list is empty, it throws IndexOutOfBoundsException
.
You could try to notify the adapter about the changes immediately after calling messageList.clear()
, but it seems to me that just deleting this clearing will solve the problem.
private void fetchMessageList(){
//messageList.clear(); <- delete this
AndroidNetworking.get(Util.url)
...
}