How to avoid an Illegalstateexception: The content of the Adapter has changed but Listview Did not receive a notification?

Asked

Viewed 235 times

12

I have a Activity that displays a ListView, to which a Adapter "backed by" a ArrayList global. If I add an element to that ArrayList, ideal is to do it in the main thread and immediately call Adapter.notifyDataSetChanged() in order to avoid a "Illegalstateexception: The content of the Adapter has changed but Listview Did not receive a notification". But this ArrayList is changed by a secondary thread, run by a Service which is not always coupled to Activity and therefore does not always have a reference to Adapter so that you can ask the main thread to perform these two operations. And then when (I imagine) the main thread comes across the altered list, the exception happens.

I see two ways to solve this:

  1. Make the Adapter global so I can call you anytime I want.

  2. Create a deep copy of the list and associate the copy to the Adapter. So that 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 call notifyDataSetChanged().

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

I also posted on ONLY in English.

  • 3

    We are discussing how to use tags in this case: http://meta.answall.com/questions/468/existem-tags-implicitas

2 answers

0

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

Note that in this solution you also need to do with your activity connect to the Service in the onCreate() to request the updated list when it is started.

  • I think I should warn you: the service and activities may be working at different times. I cannot guarantee a foreground activity all the time that the service runs to relegate to it the addition of the item to the list. And I prefer not to keep the same list in different places, I always need it updated for when an activity needs it and the service is not running.

  • That’s why when you start the activity you must connect to the Service to request the most current version of the list.

  • I see no problem in keeping two versions of the list if it is updated in only one place. That is, its implementation must ensure that the service is responsible for the most current list (type one Provider). The activity then simply makes or receives requests from the service when needed.

  • In that case the BroadcastReceiver adds a complexity to the code that could work in a simpler way with Alternative 2 of the question. In addition, a service that runs all the time without delivering continuous value is considered an anti-pattern.

  • The use of BroadcastReceiver is quite simple and costs almost nothing in memory. You have not posted any code but your second option seems to me more complex.

  • The most complicated part of solution 2 in my opinion is the deep copy. In fact the solution of BroadcastReceiver would need a deep copy to send a copy of the list to the activity instead of a reference, otherwise a IllegalStateException likewise.

  • One correction: I’m probably wrong about the need to do deep copy in the solution of BroadcastReceiver, because the extras of Intent are serialized and therefore already occurs a deep copy.

Show 2 more comments

0


The correct answer is not to change the Adapter in a Background thread, the exception itself already alerts 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 solve the problem would be to use a Contentprovider to save the received data into your service.

Already in your Activity you should use a Cursoradapter to your List/Gridview.

To ensure consistency of the list data (and keep queries out of the Thread UI), the reading of the data should be done with a Cursorloader, as this automatically registers a Contentobserver to keep your Cursor always updated.

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

For the creation of Content Provider I usually use the Contentprovidercodegenerator

  • For the general case is correct, in fact this answer already appears in the question itself... the question however refers to a specific case.

  • I don’t know what your data looks like, or how the list is updated. If you download only the new items, you could post the new items to Activity using a Message Bus, and add them to the Adapter and call notifyDatasetChanged(), or use a DB with Cursoradapter (and Cursorloader)

  • New items arrive individually, so the problem is to add an item to the list. What are the advantages and disadvantages of each option?

  • Using a Contentprovider, you can continue to populate your data in a Service background. Cursorloader already takes care of keeping the Cursoradapter updated (it registers a Contentobserver and updates the Cursor automatically)

  • I am inclined to accept this answer, although it does not mention the advantages/disadvantages of the first two options cited in the question. The Cursorloader option was also mentioned in the issue posted in the English OS and seems to be the right one, by taking advantage of the API features.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.