1

I have my own ArrayAdapter and inside of this adapter class I have a Filter. In this filter I am overriding the publishResults(CharSequence constraint, FilterResults result) method.

@Override
        protected void publishResults(CharSequence constraint, FilterResults result) {
final ArrayList<Car> filtered = (ArrayList<Car>) results.values;
for (int i = 0, l = filtered.size(); i < l; i++) {
                add(filtered.get(i));


            }
}

The method above works fine, when I only have 100 or so entries. When my entries get into the thousands, the UI Thread hangs for a few seconds. I thought I would just move this to an AsyncTask, but it crashes. What would be the best way to handle my results without having the UI hang?

Code that crashes (seems to start, and crash in the middle of the for loop:

@Override
        protected void publishResults(CharSequence constraint, FilterResults result) {
final ArrayList<Car> filtered = (ArrayList<Car>) results.values;
new LongOperation().execute(filtered);
}

    private class LongOperation extends AsyncTask<ArrayList<Car>, Void, Void> {

        @Override
        protected Void doInBackground(ArrayList<Car>... params) {
            for (int i = 0, l = params[0].size(); i < l; i++) {
                add(params[0].get(i));


            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            notifyDataSetChanged();
        }
    }
};

Here is the crash message. I am using commonsware MergeAdapter to combine two adapters, so maybe that may be the issue?

E/AndroidRuntime(22868): 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. Make sure your adapter calls notifyDataSetChanged()
    when its content changes. [in ListView(16908298, class android.widget.ListView) 
    with Adapter(class com.commonsware.cwac.merge.MergeAdapter)]
sstn
  • 3,050
  • 19
  • 32
EGHDK
  • 17,818
  • 45
  • 129
  • 204

1 Answers1

1

You cannot manipulate the adapter data in background thread. This is what the error says:

Make sure the content of your adapter is not modified from a background thread, but only from the UI thread

One solution could be to have two representation of the data, and when it change just swap between them. And make sure you are swapping on the main thread.

Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
  • 1
    Agreed. Do not call `add()` on an `ArrayAdapter` in a background thread, such as from `doInBackground()` of an `AsyncTask`. – CommonsWare Jun 27 '14 at 17:58
  • So I already have the data as the ArrayList variable "filtered". Can I just set the entire ArrayAdapter to represent this instead? I am not sure what you mean by having "two representation of the data" – EGHDK Jun 27 '14 at 17:59
  • @EGHDK yep as long you are doing this on the main thread, and do not manipulating it latter on other thread, such as from AsynTask. – Ilya Gazman Jun 27 '14 at 18:01
  • Should I set the ArrayAdapter to a new ArrayList in my publishResults() method? – EGHDK Jun 27 '14 at 18:03
  • @CommonsWare I understand now that calling add also does a notifydatasetchanged which should happen on the UI thread. I don't understand the answer provided by Ilya. I realize it's an explanation but I'm not sure how I would implement something like that in this case. Would I swap the data set (filtered) into the current arraylist in publishResults()? – EGHDK Jun 27 '14 at 18:10
  • @EGHDK: I have not attempted applying a filter to something in a `MergeAdapter`. I suspect that `MergeAdapter` may not be well-suited for that scenario. If you skip the `MergeAdapter`, in `publishResults()` you would probably create a new `ArrayAdapter` around your new `ArrayList`, then attach that new `ArrayAdapter` to the `ListView` using `setAdapter()`, replacing the original `ArrayAdapter`. – CommonsWare Jun 27 '14 at 18:15
  • @CommonsWare Hmm... I was able to get around this AND use MergeAdapter by using a filter method that allows me to implement a listener when the filtering is complete. When the filtering is complete, I create a brand new merge adapter using a newly created FilteredListAdapter. This gets rid of the slowness, but I'm not sure creating a new merge adapter every time I filter is the best thing to do. Would be easier if I could replace certain adapters in the merge adapter, but I will not dwell on that. It works! and its not slow! – EGHDK Jun 27 '14 at 19:48