java – Como evitar uma IllegalStateException: The content of the adapter has changed but ListView did not receive a notification?

Question:

I have an Activity that displays a ListView , which is associated with an Adapter "backed by" a global ArrayList . If I add an element to this ArrayList , ideally I do it in the main thread and immediately call Adapter.notifyDataSetChanged() to avoid an "IllegalStateException: The content of the adapter has changed but ListView did not receive a notification" . However, this ArrayList is changed by a secondary thread, executed by a Service that is not always coupled to the Activity and therefore does not always have a reference to the Adapter so that it can ask the main thread to execute these two operations. And then when (I imagine) the main thread is faced with the changed list, the exception ends up happening.

I see two alternatives to solve this:

  1. Make the Adapter global so I can call it whenever I want.

  2. Create a deep copy of the list and associate the copy with Adapter . So when the original list is changed the copy remains untouched until such time as the main thread needs to display the change (in Activity.onResume() , for example). Then I change the copy from the original and call notifyDataSetChanged() .

The disadvantage of the first one, in my opinion, is to make the code confused with the presence of an object outside its correct scope, as the right thing to do would be to keep it only for the lifetime of the Activity that uses it. The disadvantages of the second one are the redundancy that appears to be unnecessary and the extra memory usage (although the list is not very long, at most 500 objects of about 12 fields each). Are there other advantages and disadvantages to these two options? Is there a third option?

I also posted on the SO in English .

Answer:

The correct answer is not to change the Adapter in a Background thread, the exception itself already warns of this…

        ......
        } 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() + ")]");
        }
        ....

EDIT

One way to solve the problem would be to use a ContentProvider to save the data received in your service.

In your Activity you should use a CursorAdapter for your List/GridView.

To ensure consistency of list data (and keep queries out of the UI Thread), reading the data must be done with a CursorLoader , as this automatically registers a ContentObserver to keep your Cursor always up to date.

More information about CursorLoader in the Loading Data in Background training and about Content Providers .

For the creation of Content Provider I normally use ContentProviderCodeGenerator

Scroll to Top