Android, ListView IllegalStateException: “The content of the adapter has changed but ListView did no

来源:互联网 发布:人工智能东南大学 编辑:程序博客网 时间:2024/06/03 14:51
开发中老是遇到如下的问题:其实是有两种方案:1.一种是一定要讲耗时操作放在主线程。2.另一种是采用一个备份的list进行拷贝,防止数据更新时,没有通知UI更新
Android, ListView IllegalStateException: “The content of the adapter has changed but ListView did not receive a notification”
up vote128down votefavorite
57

What I want to do: run a background thread which calculates ListView contents and update ListView partially, while results are calculated.

What I know I have to avoid: I cannot mess with ListAdapter contents from background thread, so I inherited AsyncTask and publish result (add entries to adapter) from onProgressUpdate. My Adapter uses ArrayList of result objects, all operations on those arraylists are synchronized.

Research of other people: there is very valuable data here. I also suffered from almost daily crashes for group of ~500 users, and when I added list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) block in onProgressUpdate, crashes lowered by a factor of 10 but not disappeared. (it was suggested in answer )

What I got sometimes: please notice, it happens really rarely (once a week for one of 3.5k users). But I'd like to get rid of this bug completely. Here is partial stacktrace:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]at android.widget.ListView.layoutChildren(ListView.java:1432)at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)at android.widget.ListView.onTouchEvent(ListView.java:3234)at android.view.View.dispatchTouchEvent(View.java:3709)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)[...]

Help? Not needed anymore, see below

FINAL ANSWER: As it turned out, I was calling notifyDataSetChanged every 5 insertions to avoid flickering and sudden list changes. It cannot be done such way, always notify adapter when base list changes. This bug it long gone for me now.

shareimprove this question
 
3 
Did you call notifyDataSetChanged()? – Denis Palnitsky Jun 28 '10 at 11:49
1 
Of course, in onProgressUpdate there goes sequence: list.setVisibility(GONE) - addObjectToList/synchronized operation on list/ - notifyDataSetChanged() - list.setVisibility(VISIBLE) (and without visibility modifications exception happens far more often) – tomash Jun 28 '10 at 11:55 
1 
Are you modifying the underlying ArrayList on a different thread? All changes, even to the ArrayList that is referenced by the Adapter, have to occur on the UI thread. – Qberticus Jun 29 '10 at 3:29
1 
@Qberticus - as I clearly stated, I do not modify ArrayList from different thread, but from method onProgressUpdate of AcyncTask - it works in GUI thread. – tomash Jun 29 '10 at 5:31
1 
@tomash i got same exception i am using pull to refresh and in on post execute i have writtenadapter.notifyDataSetChanged(); lvAutherlist.completeRefreshing(); but sometimes got this error how to solve it – Khan Jan 21 '13 at 8:00

18 Answers

activeoldestvotes
up vote74down voteaccepted

I had the same issue.

I was adding items to my ArrayList outside the UI thread.

I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this.

shareimprove this answer
 
 
How you resolved it? – SweetWisher ツ Oct 11 '13 at 11:03
3 
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins Dec 12 '13 at 11:52
13 
Genial comment, really. – dentex Dec 16 '13 at 8:08
2 
This is a Multithreading Issue and Using Properly Synchronized Blocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.Check my answer below. – Javanator Jan 2 '14 at 7:50
1 
For future readers - doing calculations in background thread is a good thing, but you should pass results to UI thread and do the adding items to adapter base collection and notifying adapter in the same block of code. – tomash Apr 22 '14 at 8:09
up vote18down vote

I had the same problem, but I fixed it using the method

requestLayout();

from the class ListView

shareimprove this answer
 
 
With me work too – ademar111190 Apr 5 '12 at 20:29
24 
when should we call this method? – DeBuGGeR Feb 19 '13 at 12:59
2 
@DeBuGGeR after you either add items to the ListView or change the adapter of it. – Ahmet Noyan KızıltanJun 15 '13 at 22:34 
1 
Just for the record, @Dr.aNdRO, you collect your results in AsyncTask. doInBackground() and save your results to update the list in AsyncTask.onPostExecute(), which will run in the UI thread. If you need to update as you go, use AsyncTask.publishProgress() and AsyncTask.onProgressUpdate() which also runs in the UI thread. – Nicole Borrelli Aug 13 '14 at 18:18
1 
It worked for me, but can someone explain why? – downhand Aug 23 '15 at 20:20
up vote14down vote

This is a MultiThreading Issue and Using Properly Synchronized Blocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.

I also faced the same. And as the most accepted answer suggests making change to adapter data from UI Thread can solve the issue. That will work but is a quick and easy solution but not the best one.

As you can see for a normal case. Updating data adapter from background thread and calling notifyDataSetChanged in UI thread works.

This illegalStateException arises when a ui thread is updating the view and another background thread changes the data again. That moment causes this issue.

So if you will synchronize all the code which is changing the adapter data and making notifydatasetchange call. This issue should be gone. As gone for me and i am still updating the data from background thread.

Here is my case specific code for others to refer.

My loader on the main screen loads the phone book contacts into my data sources in the background.

    @Override    public Void loadInBackground() {        Log.v(TAG, "Init loadings contacts");        synchronized (SingleTonProvider.getInstance()) {            PhoneBookManager.preparePhoneBookContacts(getContext());        }    }

This PhoneBookManager.getPhoneBookContacts reads contact from phonebook and fills them in the hashmaps. Which is directly usable for List Adapters to draw list.

There is a button on my screen. That opens a activity where these phone numbers are listed. If i directly setAdapter over the list before the previous thread finishes its work which is fast naviagtion case happens less often. It pops up the exception .Which is title of this SO question. So i have to do something like this in the second activity.

My loader in the second activity waits for first thread to complete. Till it shows a progress bar. Check the loadInBackground of both the loaders.

Then it creates the adapter and deliver it to the activity where on ui thread i call setAdapter.

That solved my issue.

This code is a snippet only. You need to change it to compile well for you.

@Overridepublic Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {    return new PhoneBookContactLoader(this);}@Overridepublic void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {    contactList.setAdapter(adapter = arg1);}/* * AsyncLoader to load phonebook and notify the list once done. */private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {    private PhoneBookContactAdapter adapter;    public PhoneBookContactLoader(Context context) {        super(context);    }    @Override    public PhoneBookContactAdapter loadInBackground() {        synchronized (SingleTonProvider.getInstance()) {            return adapter = new PhoneBookContactAdapter(getContext());            }    }}

Hope this helps

shareimprove this answer
 
 
Sorry how did you do that? How did you syncronized the Background thread code and the notifydatasetchange call? I am using a doInBackground when filtering data on an AutocompleteTextView and I am facing the same problem...Could you advise me something in order to resolve? – tonix May 3 '14 at 20:32
 
@user3019105 - Synchronizing two background threads. one which updates the data in background and other which notifies the ui thread to setAdadpter or notifydatasetchanged using handler.post methods or runOnUiThread methods available. For my particular case I synchronized two loaders with their doInBackground synchronized on a singleton object. One start preparing data on the splash screen itself to be quickly available for the dashboard screen. That was my need. – Javanator May 4 '14 at 4:28 
 
Could you please post a snippet of code of this implementation? In my case, I have resolved calling clear() on the adapter, creating a temporary ArrayList<T> of filtered objects, then making a call to the addAll(tmpArrayList) and finally the notifyDataSetChanged(). I make all this calls inside the publishResult() Filter method, running on the main thread. Do you think this is a reliable solution? – tonix May 4 '14 at 8:36
 
@user3019105 It will gonna work but not a clean thing to do from code prospective. My inner self comes out staring at me when i am at such stages. But if you stood up. It will going to get done cleanly in the end as always. You might feel like captain America as i do feel sometimes :). You can post your question separately. Me and others can post a direct answer there. I will also update my answer here with code snippet to make it more clear. cheers – Javanator May 4 '14 at 11:45 
 
@user3019105 check the edited answer. Hope its helpful to you now – Javanator May 4 '14 at 12:15
up vote7down vote

I solved this by have 2 Lists. One list I use for only the adapter, and I do all data changes/updates on the other list. This allows me to do updates on one list in a background thread, and then update the "adapter" list in the main/UI thread:

List<> data = new ArrayList<>();List<> adapterData = new ArrayList();...adapter = new Adapter(adapterData);listView.setAdapter(adapter);// Whenever data needs to be updated, it can be done in a separate threadvoid updateDataAsync(){    new Thread(new Runnable()    {        @Override        public void run()        {            // Make updates the "data" list.            ...            // Update your adapter.            refreshList();        }    }).start();}void refreshList(){    runOnUiThread(new Runnable()    {        @Override        public void run()        {            adapterData.clear();            adapterData.addAll(data);            adapter.notifyDataSetChanged();            listView.invalidateViews();        }    });}
shareimprove this answer
 
 
Awesome! Just fixed this long pending bug with your solution, working perfectly now. – xDragonZ Nov 5 '15 at 7:23
up vote6down vote

I wrote this code and had it run in a 2.1 emulator image for ~12 hours and did not get the IllegalStateException. I'm going to give the android framework the benefit of the doubt on this one and say that it is most likely an error in your code. I hope this helps. Maybe you can adapt it to your list and data.

public class ListViewStressTest extends ListActivity {    ArrayAdapter<String> adapter;    ListView list;    AsyncTask<Void, String, Void> task;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);        this.list = this.getListView();        this.list.setAdapter(this.adapter);        this.task = new AsyncTask<Void, String, Void>() {            Random r = new Random();            int[] delete;            volatile boolean scroll = false;            @Override            protected void onProgressUpdate(String... values) {                if(scroll) {                    scroll = false;                    doScroll();                    return;                }                if(values == null) {                    doDelete();                    return;                }                doUpdate(values);                if(ListViewStressTest.this.adapter.getCount() > 5000) {                    ListViewStressTest.this.adapter.clear();                }            }            private void doScroll() {                if(ListViewStressTest.this.adapter.getCount() == 0) {                    return;                }                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());                ListViewStressTest.this.list.setSelection(n);            }            private void doDelete() {                int[] d;                synchronized(this) {                    d = this.delete;                }                if(d == null) {                    return;                }                for(int i = 0 ; i < d.length ; i++) {                    int index = d[i];                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));                    }                }            }            private void doUpdate(String... values) {                for(int i = 0 ; i < values.length ; i++) {                    ListViewStressTest.this.adapter.add(values[i]);                }            }            private void updateList() {                int number = r.nextInt(30) + 1;                String[] strings = new String[number];                for(int i = 0 ; i < number ; i++) {                    strings[i] = Long.toString(r.nextLong());                }                this.publishProgress(strings);            }            private void deleteFromList() {                int number = r.nextInt(20) + 1;                int[] toDelete = new int[number];                for(int i = 0 ; i < number ; i++) {                    int num = ListViewStressTest.this.adapter.getCount();                    if(num < 2) {                        break;                    }                    toDelete[i] = r.nextInt(num);                }                synchronized(this) {                    this.delete = toDelete;                }                this.publishProgress(null);            }            private void scrollSomewhere() {                this.scroll = true;                this.publishProgress(null);            }            @Override            protected Void doInBackground(Void... params) {                while(true) {                    int what = r.nextInt(3);                    switch(what) {                        case 0:                            updateList();                            break;                        case 1:                            deleteFromList();                            break;                        case 2:                            scrollSomewhere();                            break;                    }                    try {                        Thread.sleep(0);                    } catch(InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        };        this.task.execute(null);    }}
shareimprove this answer
 
 
Thank you for your work! I noticed that for ArrayAdapter method hasStableIds()=false; my implementation returned true which was not quite right, as rows were sorted before every notifyDataSetChanged(). I'll give it a try - if crashes survives, I will continue investigation. Best regards! – tomash Jun 29 '10 at 23:15
up vote3down vote

My issue was related to the use of a Filter together with the ListView.

When setting or updating the underlying data model of the ListView, I was doing something like this:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter){    this.allContacts = newContacts;    this.filteredContacts = newContacts;    getFilter().filter(filter);}

Calling filter() in the last line will (and must) cause notifyDataSetChanged() to be called in the Filter's publishResults() method. This may work okay sometimes, specially in my fast Nexus 5. But in reality, it's hiding a bug that you will notice with slower devices or in resource intensive conditions.

The problem is that the filtering is done asynchronously, and thus between the end of the filter()statement and the call to publishResults(), both in the UI thread, some other UI thread code may execute and change the content of the adapter.

The actual fix is easy, just call notifyDataSetChanged() also before requesting the filtering to be performed:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter){    this.allContacts = newContacts;    this.filteredContacts = newContacts;    notifyDataSetChanged(); // Fix    getFilter().filter(filter);}
shareimprove this answer
 
up vote2down vote

Even I faced the same problem in my XMPP notification application, receivers message needs to be added back to list view (implemented with ArrayList). When I tried to add the receiver content through MessageListener (seperate thread), application quits with above error. I solved this by adding the content to my arraylist & setListviewadapater through runOnUiThread method which is part of Activity class. This solveed my problem.

shareimprove this answer
 
up vote2down vote

I'm was facing the same problem with exactly the same error log. In my case onProgress() of the AsyncTask adds the values to the adapter using mAdapter.add(newEntry). To avoid the UI becoming less responsive I set mAdapter.setNotifyOnChange(false) and call mAdapter.notifyDataSetChanged() 4 times the second. Once per second the array is sorted.

This work well and looks very addictive, but unfortunately it is possible to crash it by touching the shown list items often enough.

But it seems I have found an acceptable workaround. My guess it that even if you just work on the ui thread the adapter does not accept many changes to it's data without calling notifyDataSetChanged(), because of this I created a queue that is storing all the new items until the mentioned 300ms are over. If this moment is reached I add all the stored items in one shot and call notifyDataSetChanged(). Until now I was not able to crash the list anymore.

shareimprove this answer
 
up vote2down vote

I have a List if Feed objects. It's appended and truncated from none-UI thread. It works fine with adapter below. I call FeedAdapter.notifyDataSetChanged in UI thread anyway but little bit later. I do like this because my Feed objects stay in memory in Local Service even when UI is dead.

public class FeedAdapter extends BaseAdapter {    private int size = 0;    private final List<Feed> objects;    public FeedAdapter(Activity context, List<Feed> objects) {        this.context = context;        this.objects = objects;        size = objects.size();    }    public View getView(int position, View convertView, ViewGroup parent) {        ...    }    @Override    public void notifyDataSetChanged() {        size = objects.size();        super.notifyDataSetChanged();    }    @Override    public int getCount() {        return size;    }    @Override    public Object getItem(int position) {        try {            return objects.get(position);        } catch (Error e) {            return Feed.emptyFeed;        }    }    @Override    public long getItemId(int position) {        return position;    }}
shareimprove this answer
 
up vote1down vote

I had the same problem and I solved it. My problem was that I was using a listview, with an array adapter and with filter. On the method performFiltering I was messing with the array that have the data and it was the problem since this method is not running on the UI thread and EVENTUALLY it raises some problems.

shareimprove this answer
 
up vote1down vote

One cause for this crash is that ArrayList object cannot change completely. So, when I remove an item, I have to do this:

mList.clear(); mList.addAll(newDataList);

This fixed the crash for me.

shareimprove this answer
 
up vote1down vote

In my case I called the method GetFilter () on an adapter from the TextWatcher() method on main Activity, and I added the data with a For loop on GetFilter(). The solution was change the For loop to AfterTextChanged() submethod on main Activity and delete the call to GetFilter()

shareimprove this answer
 
1 
Please clarify your solution. I`m also in situation looks like u/ – nAkhmedov Nov 27 '14 at 14:24
up vote1down vote

Had this happen intermittently, turns out I only had this issue when the list was scrolled after a 'load more' last item was clicked. If the list wasn't scrolled, everything worked fine.

After MUCH debugging, it was a bug on my part, but an inconsistency in the Android code also.

When the validation happens, this code is executed in ListView

        } else if (mItemCount != mAdapter.getCount()) {            throw new IllegalStateException("The content of the adapter has changed but "                    + "ListView did not receive a notification. Make sure the content of "

But when onChange happens it fires this code in AdapterView (parent of ListView)

    @Override    public void onChanged() {        mDataChanged = true;        mOldItemCount = mItemCount;        mItemCount = getAdapter().getCount();

Notice the way the Adapter is NOT guaranteed to be the Same!

In my case, since it was a 'LoadMoreAdapter' I was returning the WrappedAdapter in the getAdapter call (for access to the underlying objects). This resulted in the counts being different due to the extra 'Load More' item and the Exception being thrown.

I only did this because the docs make it seem like it's ok to do

ListView.getAdapter javadoc

Returns the adapter currently in use in this ListView. The returned adapter might not be the same adapter passed to setAdapter(ListAdapter) but might be a WrapperListAdapter.

shareimprove this answer
 
 
I am having a similar issue . Can you please suggest how to fix it. – May13ank May 14 '15 at 8:59
 
If you look at the 2 code samples above you'll see mAdapter in ListView and getAdapter() in a parent of ListView (AdapterView). If you override getAdapter(), the result of getAdapter() may not be the mAdapter. If the counts of the 2 Adapters are different, you get the error. – aaronvargas May 15 '15 at 21:57
up vote1down vote

This is a known bug in Android 4 to 4.4(KitKat) and is resolved in ">4.4"

See here: https://code.google.com/p/android/issues/detail?id=71936

shareimprove this answer
 
up vote0down vote

Please try one of these solutions :

  1. Sometimes, if you add new object to data list in a thread (or doInBackground method), this error will occur. The solution is : create a temporary list and do adding data to this list in thread(or doInBackground), then do copying all data from temporary list to the list of adapter in UI thread (or onPostExcute)

  2. Make sure all UI updates are called in UI thread.

shareimprove this answer
 
up vote0down vote

i haved same problem when add new data in lazy image loader i just put

         adapter.notifyDataSetChanged();

in

       protected void onPostExecute(Void args) {        adapter.notifyDataSetChanged();        // Close the progressdialog        mProgressDialog.dismiss();         }

hope tohel you

shareimprove this answer
 
up vote0down vote

Like @Mullins said "
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins".

In my case I have asynctask and I called notifyDataSetChanged() in the doInBackground() method and the problem is solved, when I called from onPostExecute() I received the exception.

shareimprove this answer
 
up vote0down vote

I had a custom ListAdapter and was calling super.notifyDataSetChanged() at the beginning and not the end of the method

@Overridepublic void notifyDataSetChanged() {    recalculate();    super.notifyDataSetChanged();}
shareimprove this answer
 

Your Answer

 

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged android exception listview adapter or ask 

_Android, ListView IllegalStateException: “The content of the adapter has changed but ListView did not receive a notification”
up vote128down votefavorite
57

What I want to do: run a background thread which calculates ListView contents and update ListView partially, while results are calculated.

What I know I have to avoid: I cannot mess with ListAdapter contents from background thread, so I inherited AsyncTask and publish result (add entries to adapter) from onProgressUpdate. My Adapter uses ArrayList of result objects, all operations on those arraylists are synchronized.

Research of other people: there is very valuable data here. I also suffered from almost daily crashes for group of ~500 users, and when I added list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) block in onProgressUpdate, crashes lowered by a factor of 10 but not disappeared. (it was suggested in answer )

What I got sometimes: please notice, it happens really rarely (once a week for one of 3.5k users). But I'd like to get rid of this bug completely. Here is partial stacktrace:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]at android.widget.ListView.layoutChildren(ListView.java:1432)at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)at android.widget.ListView.onTouchEvent(ListView.java:3234)at android.view.View.dispatchTouchEvent(View.java:3709)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)[...]

Help? Not needed anymore, see below

FINAL ANSWER: As it turned out, I was calling notifyDataSetChanged every 5 insertions to avoid flickering and sudden list changes. It cannot be done such way, always notify adapter when base list changes. This bug it long gone for me now.

shareimprove this question
 
3 
Did you call notifyDataSetChanged()? – Denis Palnitsky Jun 28 '10 at 11:49
1 
Of course, in onProgressUpdate there goes sequence: list.setVisibility(GONE) - addObjectToList/synchronized operation on list/ - notifyDataSetChanged() - list.setVisibility(VISIBLE) (and without visibility modifications exception happens far more often) – tomash Jun 28 '10 at 11:55 
1 
Are you modifying the underlying ArrayList on a different thread? All changes, even to the ArrayList that is referenced by the Adapter, have to occur on the UI thread. – Qberticus Jun 29 '10 at 3:29
1 
@Qberticus - as I clearly stated, I do not modify ArrayList from different thread, but from method onProgressUpdate of AcyncTask - it works in GUI thread. – tomash Jun 29 '10 at 5:31
1 
@tomash i got same exception i am using pull to refresh and in on post execute i have writtenadapter.notifyDataSetChanged(); lvAutherlist.completeRefreshing(); but sometimes got this error how to solve it – Khan Jan 21 '13 at 8:00

18 Answers

activeoldestvotes
up vote74down voteaccepted

I had the same issue.

I was adding items to my ArrayList outside the UI thread.

I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this.

shareimprove this answer
 
 
How you resolved it? – SweetWisher ツ Oct 11 '13 at 11:03
3 
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins Dec 12 '13 at 11:52
13 
Genial comment, really. – dentex Dec 16 '13 at 8:08
2 
This is a Multithreading Issue and Using Properly Synchronized Blocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.Check my answer below. – Javanator Jan 2 '14 at 7:50
1 
For future readers - doing calculations in background thread is a good thing, but you should pass results to UI thread and do the adding items to adapter base collection and notifying adapter in the same block of code. – tomash Apr 22 '14 at 8:09
up vote18down vote

I had the same problem, but I fixed it using the method

requestLayout();

from the class ListView

shareimprove this answer
 
 
With me work too – ademar111190 Apr 5 '12 at 20:29
24 
when should we call this method? – DeBuGGeR Feb 19 '13 at 12:59
2 
@DeBuGGeR after you either add items to the ListView or change the adapter of it. – Ahmet Noyan KızıltanJun 15 '13 at 22:34 
1 
Just for the record, @Dr.aNdRO, you collect your results in AsyncTask. doInBackground() and save your results to update the list in AsyncTask.onPostExecute(), which will run in the UI thread. If you need to update as you go, use AsyncTask.publishProgress() and AsyncTask.onProgressUpdate() which also runs in the UI thread. – Nicole Borrelli Aug 13 '14 at 18:18
1 
It worked for me, but can someone explain why? – downhand Aug 23 '15 at 20:20
up vote14down vote

This is a MultiThreading Issue and Using Properly Synchronized Blocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.

I also faced the same. And as the most accepted answer suggests making change to adapter data from UI Thread can solve the issue. That will work but is a quick and easy solution but not the best one.

As you can see for a normal case. Updating data adapter from background thread and calling notifyDataSetChanged in UI thread works.

This illegalStateException arises when a ui thread is updating the view and another background thread changes the data again. That moment causes this issue.

So if you will synchronize all the code which is changing the adapter data and making notifydatasetchange call. This issue should be gone. As gone for me and i am still updating the data from background thread.

Here is my case specific code for others to refer.

My loader on the main screen loads the phone book contacts into my data sources in the background.

    @Override    public Void loadInBackground() {        Log.v(TAG, "Init loadings contacts");        synchronized (SingleTonProvider.getInstance()) {            PhoneBookManager.preparePhoneBookContacts(getContext());        }    }

This PhoneBookManager.getPhoneBookContacts reads contact from phonebook and fills them in the hashmaps. Which is directly usable for List Adapters to draw list.

There is a button on my screen. That opens a activity where these phone numbers are listed. If i directly setAdapter over the list before the previous thread finishes its work which is fast naviagtion case happens less often. It pops up the exception .Which is title of this SO question. So i have to do something like this in the second activity.

My loader in the second activity waits for first thread to complete. Till it shows a progress bar. Check the loadInBackground of both the loaders.

Then it creates the adapter and deliver it to the activity where on ui thread i call setAdapter.

That solved my issue.

This code is a snippet only. You need to change it to compile well for you.

@Overridepublic Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {    return new PhoneBookContactLoader(this);}@Overridepublic void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {    contactList.setAdapter(adapter = arg1);}/* * AsyncLoader to load phonebook and notify the list once done. */private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {    private PhoneBookContactAdapter adapter;    public PhoneBookContactLoader(Context context) {        super(context);    }    @Override    public PhoneBookContactAdapter loadInBackground() {        synchronized (SingleTonProvider.getInstance()) {            return adapter = new PhoneBookContactAdapter(getContext());            }    }}

Hope this helps

shareimprove this answer
 
 
Sorry how did you do that? How did you syncronized the Background thread code and the notifydatasetchange call? I am using a doInBackground when filtering data on an AutocompleteTextView and I am facing the same problem...Could you advise me something in order to resolve? – tonix May 3 '14 at 20:32
 
@user3019105 - Synchronizing two background threads. one which updates the data in background and other which notifies the ui thread to setAdadpter or notifydatasetchanged using handler.post methods or runOnUiThread methods available. For my particular case I synchronized two loaders with their doInBackground synchronized on a singleton object. One start preparing data on the splash screen itself to be quickly available for the dashboard screen. That was my need. – Javanator May 4 '14 at 4:28 
 
Could you please post a snippet of code of this implementation? In my case, I have resolved calling clear() on the adapter, creating a temporary ArrayList<T> of filtered objects, then making a call to the addAll(tmpArrayList) and finally the notifyDataSetChanged(). I make all this calls inside the publishResult() Filter method, running on the main thread. Do you think this is a reliable solution? – tonix May 4 '14 at 8:36
 
@user3019105 It will gonna work but not a clean thing to do from code prospective. My inner self comes out staring at me when i am at such stages. But if you stood up. It will going to get done cleanly in the end as always. You might feel like captain America as i do feel sometimes :). You can post your question separately. Me and others can post a direct answer there. I will also update my answer here with code snippet to make it more clear. cheers – Javanator May 4 '14 at 11:45 
 
@user3019105 check the edited answer. Hope its helpful to you now – Javanator May 4 '14 at 12:15
up vote7down vote

I solved this by have 2 Lists. One list I use for only the adapter, and I do all data changes/updates on the other list. This allows me to do updates on one list in a background thread, and then update the "adapter" list in the main/UI thread:

List<> data = new ArrayList<>();List<> adapterData = new ArrayList();...adapter = new Adapter(adapterData);listView.setAdapter(adapter);// Whenever data needs to be updated, it can be done in a separate threadvoid updateDataAsync(){    new Thread(new Runnable()    {        @Override        public void run()        {            // Make updates the "data" list.            ...            // Update your adapter.            refreshList();        }    }).start();}void refreshList(){    runOnUiThread(new Runnable()    {        @Override        public void run()        {            adapterData.clear();            adapterData.addAll(data);            adapter.notifyDataSetChanged();            listView.invalidateViews();        }    });}
shareimprove this answer
 
 
Awesome! Just fixed this long pending bug with your solution, working perfectly now. – xDragonZ Nov 5 '15 at 7:23
up vote6down vote

I wrote this code and had it run in a 2.1 emulator image for ~12 hours and did not get the IllegalStateException. I'm going to give the android framework the benefit of the doubt on this one and say that it is most likely an error in your code. I hope this helps. Maybe you can adapt it to your list and data.

public class ListViewStressTest extends ListActivity {    ArrayAdapter<String> adapter;    ListView list;    AsyncTask<Void, String, Void> task;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);        this.list = this.getListView();        this.list.setAdapter(this.adapter);        this.task = new AsyncTask<Void, String, Void>() {            Random r = new Random();            int[] delete;            volatile boolean scroll = false;            @Override            protected void onProgressUpdate(String... values) {                if(scroll) {                    scroll = false;                    doScroll();                    return;                }                if(values == null) {                    doDelete();                    return;                }                doUpdate(values);                if(ListViewStressTest.this.adapter.getCount() > 5000) {                    ListViewStressTest.this.adapter.clear();                }            }            private void doScroll() {                if(ListViewStressTest.this.adapter.getCount() == 0) {                    return;                }                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());                ListViewStressTest.this.list.setSelection(n);            }            private void doDelete() {                int[] d;                synchronized(this) {                    d = this.delete;                }                if(d == null) {                    return;                }                for(int i = 0 ; i < d.length ; i++) {                    int index = d[i];                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));                    }                }            }            private void doUpdate(String... values) {                for(int i = 0 ; i < values.length ; i++) {                    ListViewStressTest.this.adapter.add(values[i]);                }            }            private void updateList() {                int number = r.nextInt(30) + 1;                String[] strings = new String[number];                for(int i = 0 ; i < number ; i++) {                    strings[i] = Long.toString(r.nextLong());                }                this.publishProgress(strings);            }            private void deleteFromList() {                int number = r.nextInt(20) + 1;                int[] toDelete = new int[number];                for(int i = 0 ; i < number ; i++) {                    int num = ListViewStressTest.this.adapter.getCount();                    if(num < 2) {                        break;                    }                    toDelete[i] = r.nextInt(num);                }                synchronized(this) {                    this.delete = toDelete;                }                this.publishProgress(null);            }            private void scrollSomewhere() {                this.scroll = true;                this.publishProgress(null);            }            @Override            protected Void doInBackground(Void... params) {                while(true) {                    int what = r.nextInt(3);                    switch(what) {                        case 0:                            updateList();                            break;                        case 1:                            deleteFromList();                            break;                        case 2:                            scrollSomewhere();                            break;                    }                    try {                        Thread.sleep(0);                    } catch(InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        };        this.task.execute(null);    }}
shareimprove this answer
 
 
Thank you for your work! I noticed that for ArrayAdapter method hasStableIds()=false; my implementation returned true which was not quite right, as rows were sorted before every notifyDataSetChanged(). I'll give it a try - if crashes survives, I will continue investigation. Best regards! – tomash Jun 29 '10 at 23:15
up vote3down vote

My issue was related to the use of a Filter together with the ListView.

When setting or updating the underlying data model of the ListView, I was doing something like this:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter){    this.allContacts = newContacts;    this.filteredContacts = newContacts;    getFilter().filter(filter);}

Calling filter() in the last line will (and must) cause notifyDataSetChanged() to be called in the Filter's publishResults() method. This may work okay sometimes, specially in my fast Nexus 5. But in reality, it's hiding a bug that you will notice with slower devices or in resource intensive conditions.

The problem is that the filtering is done asynchronously, and thus between the end of the filter()statement and the call to publishResults(), both in the UI thread, some other UI thread code may execute and change the content of the adapter.

The actual fix is easy, just call notifyDataSetChanged() also before requesting the filtering to be performed:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter){    this.allContacts = newContacts;    this.filteredContacts = newContacts;    notifyDataSetChanged(); // Fix    getFilter().filter(filter);}
shareimprove this answer
 
up vote2down vote

Even I faced the same problem in my XMPP notification application, receivers message needs to be added back to list view (implemented with ArrayList). When I tried to add the receiver content through MessageListener (seperate thread), application quits with above error. I solved this by adding the content to my arraylist & setListviewadapater through runOnUiThread method which is part of Activity class. This solveed my problem.

shareimprove this answer
 
up vote2down vote

I'm was facing the same problem with exactly the same error log. In my case onProgress() of the AsyncTask adds the values to the adapter using mAdapter.add(newEntry). To avoid the UI becoming less responsive I set mAdapter.setNotifyOnChange(false) and call mAdapter.notifyDataSetChanged() 4 times the second. Once per second the array is sorted.

This work well and looks very addictive, but unfortunately it is possible to crash it by touching the shown list items often enough.

But it seems I have found an acceptable workaround. My guess it that even if you just work on the ui thread the adapter does not accept many changes to it's data without calling notifyDataSetChanged(), because of this I created a queue that is storing all the new items until the mentioned 300ms are over. If this moment is reached I add all the stored items in one shot and call notifyDataSetChanged(). Until now I was not able to crash the list anymore.

shareimprove this answer
 
up vote2down vote

I have a List if Feed objects. It's appended and truncated from none-UI thread. It works fine with adapter below. I call FeedAdapter.notifyDataSetChanged in UI thread anyway but little bit later. I do like this because my Feed objects stay in memory in Local Service even when UI is dead.

public class FeedAdapter extends BaseAdapter {    private int size = 0;    private final List<Feed> objects;    public FeedAdapter(Activity context, List<Feed> objects) {        this.context = context;        this.objects = objects;        size = objects.size();    }    public View getView(int position, View convertView, ViewGroup parent) {        ...    }    @Override    public void notifyDataSetChanged() {        size = objects.size();        super.notifyDataSetChanged();    }    @Override    public int getCount() {        return size;    }    @Override    public Object getItem(int position) {        try {            return objects.get(position);        } catch (Error e) {            return Feed.emptyFeed;        }    }    @Override    public long getItemId(int position) {        return position;    }}
shareimprove this answer
 
up vote1down vote

I had the same problem and I solved it. My problem was that I was using a listview, with an array adapter and with filter. On the method performFiltering I was messing with the array that have the data and it was the problem since this method is not running on the UI thread and EVENTUALLY it raises some problems.

shareimprove this answer
 
up vote1down vote

One cause for this crash is that ArrayList object cannot change completely. So, when I remove an item, I have to do this:

mList.clear(); mList.addAll(newDataList);

This fixed the crash for me.

shareimprove this answer
 
up vote1down vote

In my case I called the method GetFilter () on an adapter from the TextWatcher() method on main Activity, and I added the data with a For loop on GetFilter(). The solution was change the For loop to AfterTextChanged() submethod on main Activity and delete the call to GetFilter()

shareimprove this answer
 
1 
Please clarify your solution. I`m also in situation looks like u/ – nAkhmedov Nov 27 '14 at 14:24
up vote1down vote

Had this happen intermittently, turns out I only had this issue when the list was scrolled after a 'load more' last item was clicked. If the list wasn't scrolled, everything worked fine.

After MUCH debugging, it was a bug on my part, but an inconsistency in the Android code also.

When the validation happens, this code is executed in ListView

        } else if (mItemCount != mAdapter.getCount()) {            throw new IllegalStateException("The content of the adapter has changed but "                    + "ListView did not receive a notification. Make sure the content of "

But when onChange happens it fires this code in AdapterView (parent of ListView)

    @Override    public void onChanged() {        mDataChanged = true;        mOldItemCount = mItemCount;        mItemCount = getAdapter().getCount();

Notice the way the Adapter is NOT guaranteed to be the Same!

In my case, since it was a 'LoadMoreAdapter' I was returning the WrappedAdapter in the getAdapter call (for access to the underlying objects). This resulted in the counts being different due to the extra 'Load More' item and the Exception being thrown.

I only did this because the docs make it seem like it's ok to do

ListView.getAdapter javadoc

Returns the adapter currently in use in this ListView. The returned adapter might not be the same adapter passed to setAdapter(ListAdapter) but might be a WrapperListAdapter.

shareimprove this answer
 
 
I am having a similar issue . Can you please suggest how to fix it. – May13ank May 14 '15 at 8:59
 
If you look at the 2 code samples above you'll see mAdapter in ListView and getAdapter() in a parent of ListView (AdapterView). If you override getAdapter(), the result of getAdapter() may not be the mAdapter. If the counts of the 2 Adapters are different, you get the error. – aaronvargas May 15 '15 at 21:57
up vote1down vote

This is a known bug in Android 4 to 4.4(KitKat) and is resolved in ">4.4"

See here: https://code.google.com/p/android/issues/detail?id=71936

shareimprove this answer
 
up vote0down vote

Please try one of these solutions :

  1. Sometimes, if you add new object to data list in a thread (or doInBackground method), this error will occur. The solution is : create a temporary list and do adding data to this list in thread(or doInBackground), then do copying all data from temporary list to the list of adapter in UI thread (or onPostExcute)

  2. Make sure all UI updates are called in UI thread.

shareimprove this answer
 
up vote0down vote

i haved same problem when add new data in lazy image loader i just put

         adapter.notifyDataSetChanged();

in

       protected void onPostExecute(Void args) {        adapter.notifyDataSetChanged();        // Close the progressdialog        mProgressDialog.dismiss();         }

hope tohel you

shareimprove this answer
 
up vote0down vote

Like @Mullins said "
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins".

In my case I have asynctask and I called notifyDataSetChanged() in the doInBackground() method and the problem is solved, when I called from onPostExecute() I received the exception.

shareimprove this answer
 
up vote0down vote

I had a custom ListAdapter and was calling super.notifyDataSetChanged() at the beginning and not the end of the method

@Overridepublic void notifyDataSetChanged() {    recalculate();    super.notifyDataSetChanged();}
shareimprove this answer
 



0 0
原创粉丝点击