How to avoid an IllegalStateException: The content of the adapter has changed but ListView did not receive a notification?

12

I have Activity that displays a ListView , which is associated with Adapter "backed by" ArrayList global. If I add an element to this ArrayList , ideally I do it on 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 Activity and therefore does not always have a reference to Adapter so that it can ask the main thread to execute these two operations . And then when (I imagine) the main thread comes across the changed list, the exception ends up happening.

I see two alternatives to resolve this:

  • Make Adapter global so I can call you whenever I want.

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

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

    I've also posted on SO in English .

        
    asked by anonymous 14.01.2014 / 12:39

    2 answers

    0

    The correct answer is not to change the Adapter in a Background thread, the exception itself already alert to 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 work around this would be to use a ContentProvider to save the data you've received into your service.

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

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

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

    For the creation of the Content Provider I usually use the ContentProviderCodeGenerator

        
    20.01.2014 / 20:22
    0

    You can create a BroadcastReceiver in your activity and have the service send the new ArrayList as an extra of Intent to it. Hence the receiver processes the new list and calls the Adapter.notifyDataSetChanged() .

    Note that in this solution you also need to have your activity connect to Service in onCreate() to make the list request updated when it starts.

        
    16.01.2014 / 18:35