Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android -The content of the adapter has changed but ListView did not receive a notification

I get this error : 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(2131034188, class android.widget.ListView) with Adapter(class .MainActivity$ListAdapter)]

This is what I do , I run a AsyncTask and get json data , then , onPostExecute, I call ListAdapter that makes the data to listview .

@Override
        protected Void doInBackground(Void... arg0) {
            Spots_tab1_json sh = new Spots_tab1_json();
            String jsonStr = sh.makeServiceCall(url + page, Spots_tab1_json.GET);

            if (jsonStr != null) {
JSONObject jsonObj = new JSONObject(jsonStr);
                    contacts = jsonObj.getJSONArray(TAG_CONTACTS);
for (int i = 0; i < contacts.length(); i++) {
                        JSONObject c = contacts.getJSONObject(i);
                        String onvan = new String(c.getString("onvan").getBytes("ISO-8859-1"), "UTF-8");
                        String id = new String(c.getString("id").getBytes("ISO-8859-1"), "UTF-8");
                        String dates = new String(c.getString("dates").getBytes("ISO-8859-1"), "UTF-8");
                        String price = new String(c.getString("gheymat").getBytes("ISO-8859-1"), "UTF-8");
                        HashMap<String, String> contact = new HashMap<String, String>();
                        contact.put("onvan", onvan);
                        contact.put("img", new String(c.getString("img").getBytes("ISO-8859-1"), "UTF-8"));
                        contactList.add(contact);
                    }
}
}
} 

    @Override
    protected void onPostExecute(Void result) {
        super.onPostExecute(result);
            if (!isCancelled() && goterr == false) {
            final ListAdapter ladap=new ListAdapter(MainActivity.this, contactList);
            lv.setAdapter(ladap);
            runOnUiThread(new Runnable() {
                public void run() {
                    ladap.notifyDataSetChanged();
                }});
        }
    }

and after that, my listView is built. I've tested it on several different devices , non of them had any problem but some users told me and I logged and see this error.

What should I do to solve it ? what is wrong ?

thanks you

like image 766
Navid Abutorab Avatar asked Oct 23 '14 05:10

Navid Abutorab


2 Answers

Apologies for the belated reply, but it appears as suspected in my earlier comment: you're modifying contactList from two different threads.

ListView caches the element count and whenever it has to layout its children, it'll check this count againt the current number of items in the bound adapter. If the count has changed and the ListView wasn't notified about this, an error is thrown:

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 "
    + "your adapter is not modified from a background thread, but only from "
    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
    + "when its content changes. [in ListView(" + getId() + ", " + getClass()
    + ") with Adapter(" + mAdapter.getClass() + ")]");
}

Source.

In other words: that's exactly the error you're getting. In your scenario, the count difference is being caused by modifying the backing dataset from more than a single thread, leading to a synchronization issue: the background thread may modify the dataset, get suspended, and the ui thread may then call layoutChildren() without having been notified about the dataset changes.

Now, onto the solution. The easiest one is to make sure you're not modifying the list that is bound to the ListView's adapter from different threads. That is, either make a copy that you can then freely modify, or allocate a new list.

So, do something like this, i.e. in the onPreExecute() method of the AsyncTask.

New list:

List<HashMap<String, String>> mNewContactList = new ArrayList<HashMap<String, String>>();

A (shallow) copy:

List<HashMap<String, String>> mNewContactList = new ArrayList<HashMap<String, String>>(contactList);

Then, in doInBackground(), add the data to mNewContactList. Finally, in onPostExecute(), you can:

  1. Create a new adapter using mNewContactList and set it to the ListView.
  2. Or (if you didn't make a copy of the original list) add the contents of mNewContactList to the already existing contactList and call notifyDatasetChanged() on the ListView.
like image 183
MH. Avatar answered Sep 27 '22 18:09

MH.


data update and your adapter calls notifyDataSetChanged() must in the same code block. for example:

Correct case:

runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSelectedImages.add(image);
                mAdapter.notifyDataSetChanged();
            }
        });

Wrong case:

mSelectedImages.add(image);
runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mAdapter.notifyDataSetChanged();
            }
        });
like image 43
jp1017 Avatar answered Sep 27 '22 19:09

jp1017