116

I am Trying to remove my item from recyclerview, but i always getting error

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

i am using notify datasetchanged, can i solve this?

public class AdapterIntransit extends RecyclerView.Adapter<AdapterIntransit.ViewHolder> {
    private Context context;
    List<DataIntransit> data;

    public AdapterIntransit(Context context, List<DataIntransit> data) {
        this.context = context;
        this.data = data;
    }

    @Override
    public AdapterIntransit.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardintransit, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(AdapterIntransit.ViewHolder holder, int position) {
        if (data.get(position).getJml1() - data.get(position).getJml2() <= 0) {
            data.remove(position);
            notifyItemRemoved(position);
            notifyItemRangeChanged(position, getItemCount());
            notifyDataSetChanged();
        } else {
            holder.kode.setText(data.get(position).getKode());
            holder.nama.setText(data.get(position).getNama());
            holder.jumlah.setText(String.valueOf(data.get(position).getJml1() - data.get(position).getJml2()));
        }
    }

    @Override
    public int getItemCount() {
        return data.size();
    }
    public class ViewHolder extends RecyclerView.ViewHolder{
        TextView kode, nama, jumlah;
        public ViewHolder(View itemView) {
            super(itemView);
            kode = (TextView)itemView.findViewById(R.id.kode);
            nama = (TextView)itemView.findViewById(R.id.nama);
            jumlah = (TextView)itemView.findViewById(R.id.jumlah);

        }
    }
}
Denny Kurniawan
  • 1,581
  • 2
  • 15
  • 28
  • 1
    can you explain more about why you need remove item inside `onBindViewHolder`. I think you can simple don't add it to your RecyclerView – Linh Apr 05 '17 at 04:34
  • @PhanVanLinh, i think because, can access position from `onBindViewHolder` – Denny Kurniawan Apr 05 '17 at 04:39
  • Does this answer your question? [Android RecyclerView : notifyDataSetChanged() IllegalStateException](https://stackoverflow.com/questions/27070220/android-recyclerview-notifydatasetchanged-illegalstateexception) – Sam Oct 15 '21 at 11:52

10 Answers10

150

Below answer worked for me

This is just workaround for the problem.

This usually occurs when you are calling notifyDataSetChanged() on the background thread. So just move notify to UI thread

recyclerView.post(new Runnable()
            {
              @Override
              public void run() {
                myadapter.notifyDataSetChanged();
              }
            });

You use your RecyclerView instance and inside the post method a new Runnable added to the message queue. The runnable will be run on the user interface thread. This is a limit for Android to access the UI thread from background (e.g. inside a method which will be run in a background thread). for more you run it on UI thread if you needed.

For more you can run it on UI thread, if you needed

 runOnUiThread(new Runnable(){
 public void run() {
      // UI code goes here
 }
 });
Sagar
  • 5,273
  • 4
  • 37
  • 50
  • 4
    This seems to be a bug in the RecyclerView library, when `notifyDataSetChanged()` is called either in quick succession (within 50 ms) or whilst scrolling -- even if it's called outside of `onBindViewHolder()`. For example, it happens when refreshing the whole list with `notifyDataSetChanged()`, not necessarily adding or removing items. The `Runnable` suggestion works well. There is a bug report here: https://issuetracker.google.com/issues/37136189 – Mr-IDE Jun 21 '19 at 09:07
  • 1
    generally speaking, doing something dependent on time for UI is a bad idea. This will work, but it is a latch rather than a solution. – Sermilion Jan 01 '20 at 20:38
  • 4
    One reason might be that you have a view in your view holder that animates. Maybe it is a switch, and after you switch, you called `notify...` – Sermilion Jan 01 '20 at 21:00
  • @Sermilion : Yes, I have a switch & toggling it, I do some operation & then notify. Should the above solution work for it ? – AndroidGuy Aug 04 '20 at 18:58
  • 2
    @AndroidGuy this will work, but this is not right way to go about it. If you have a switch, and u manually switch it, do not notify. The switch will animate on its own and you are done. If you want to programmaticly change the state of switch, then you notify, and the switch will change after notifying. – Sermilion Aug 05 '20 at 18:08
  • 1
    if this is just a workaround then what should be actual solution? – Shikhar Aug 11 '20 at 04:47
  • @Sermilion : Yes. Its working. I am doing some operation on switch toggle & programatically changing the switch & other views. – AndroidGuy Aug 11 '20 at 10:24
22

This is useful when select All checkbox on click of top checkbox in recyclerview.

recyclerview.post(new Runnable()
{
     @Override
     public void run() {
         myadapter.notifyDataSetChanged();
     }
});
Leandro Silva
  • 804
  • 1
  • 9
  • 28
Rakesh
  • 239
  • 2
  • 4
20

If you are tyring to notify recylew item then use isComputingLayout method:

if (!mRecyclerView.isComputingLayout()) 
{
 // add your code here
}
Sharanjeet Kaur
  • 796
  • 13
  • 18
  • 40
    and if it is computing layout then the request from code will never be completed? – ghita Jul 03 '20 at 10:47
  • @ghita: if it is computing then you use something like `else {Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() {@Override public void run() {myPagerAdapter.notifyItemRemoved(position);} });}` (or whatever notifyXYZ flavour you need) – ccpizza Sep 02 '23 at 11:32
14

There are two methods you need for this to work:

if (!rv.isComputingLayout && rv.scrollState == SCROLL_STATE_IDLE) {
   rv.adapter?.notifyDataSetChanged()
}
Codeversed
  • 9,287
  • 3
  • 43
  • 42
  • This should be the correct answer! If not then you might want to change your business logic to avoid this issue. – Bitwise DEVS May 10 '22 at 08:58
  • @BitwiseDEVS This shouldn't be the correct answer because it doesn't provide any direction on how to still complete the task. It discards the change altogether. While a simple app may only display data in a RecyclerView, you may eventually need to perform tasks on the RecyclerView from its views. – Abandoned Cart Jun 18 '23 at 22:07
10

You are notifying about item change inside onBindViewHolder() method, when your item in process of construction. Most probably you can just improve your logic, to avoid this.

snersesyan
  • 1,647
  • 17
  • 26
4

I give you another idea for solve your problem. I think it could be better The idea is we do not remove invalid data inside onBindViewHolder, we will remove it before

public AdapterIntransit(Context context, List < DataIntransit > data) {
    this.context = context;
    this.data = removeInValidData(data);
}

private void removeInValidData(List < DataIntransit > data) {
    for (int position = 0, position < data.size(); position++) {
        if (data.get(position).getJml1() - data.get(position).getJml2() <= 0) {
            data.remove(position);
        }
    }
}

@Override
public void onBindViewHolder(AdapterIntransit.ViewHolder holder, int position) {
    holder.kode.setText(data.get(position).getKode());
    holder.nama.setText(data.get(position).getNama());
    holder.jumlah.setText(String.valueOf(data.get(position).getJml1() - data.get(position).getJml2()));

}
Linh
  • 57,942
  • 23
  • 262
  • 279
  • Thank you sir for your answer, but i can't access position from there. – Denny Kurniawan Apr 05 '17 at 04:52
  • Just the code without the idea why the code could solve the problem is not very helpful. Please also explain the idea behind your solution. – Janusz Feb 26 '19 at 09:57
  • @Janusz thank you. updated the answer. I used to think if people have read question + check the log + check my answer, people will get the idea of this answer – Linh Feb 27 '19 at 01:19
  • This solution ONLY works if you want to filter the data at construction of the adapter – Edgar May 01 '20 at 12:32
2

When recycler view is computing layout,if we try to notify item change or data change, this issue occurs. But as per official documentation,it is written as

It is very unlikely that your code will be running during state(isComputingLayout) as it is called by the framework when a layout traversal happens or RecyclerView starts to scroll in response to system events (touch, accessibility etc).

But sometimes this issue happens leading to crash.So,better to follow as below.

if (isComputingLayout(recyclerView).not()) {
     if (Looper.myLooper() != Looper.getMainLooper()) {
          // If BG thread,then post task to recycler view
         recyclerView?.post { notifyItemChanged(position, obj) }
     } else {
         notifyItemChanged(position, obj)
     }
}
Tarun Anchala
  • 2,232
  • 16
  • 15
0

The method that worked for me the best is this

    if (this@[AdapterName].hasStableIds()) 
    {
         notifyItemChanged(position) 
    }

It check whether the id's has been set or not then only it updates the position

Aishik kirtaniya
  • 478
  • 5
  • 19
0

For some reason, doing

recyclerView.post(new Runnable() {
              @Override
              public void run() {
                adapter.notifyDataSetChanged();
              }
            });

didn't work for me. I had to use postDelayed function to finally make it work for my scenario.

recyclerView.postDelayed(
    () -> adapter.notifyItemChanged(itemChangedIndex)),
    500
);
Dhunju_likes_to_Learn
  • 1,201
  • 1
  • 16
  • 25
-1

just override this 2 fun in your recycler view

override fun getItemId(position: Int): Long {
    return position.toLong()
}

override fun getItemViewType(position: Int): Int {
    return position
}
AmirKam
  • 29
  • 1
  • 3
  • 1
    This defeats most of the point of using a `RecyclerView`, because you've effectively disabled view reuse across items of the same type. – Ryan M Aug 11 '22 at 09:56