18

11-06 19:52:25.958: E/AndroidRuntime(29609): 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(-1, class android.widget.ListPopupWindow$DropDownListView) with Adapter(class com.example.parkfoxxlight_android.PlacesAutoCompleteAdapter)]

Full log: http://pastebin.com/Hx7k28Rm

Full code of adapter: http://pastebin.com/TfH1bXE3 I am using the example from https://developers.google.com/places/training/autocomplete-android and it has quite the default code so it seems there is a bug in the google code?

The app crashes only sometimes with the above error message.

protected void publishResults(CharSequence constraint,
        FilterResults results) {

    if (results != null && results.count > 0) {
        notifyDataSetChanged();
    } else {
        notifyDataSetInvalidated();
    }
}

Activity http://pastebin.com/FYzYtvXY:

public class CityActivity extends Activity{

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.city);

            AutoCompleteTextView autoCompView = (AutoCompleteTextView) findViewById(R.id.autocomplete_city);

            PlacesAutoCompleteAdapter ad = new PlacesAutoCompleteAdapter(this);
            ProgressBar b = (ProgressBar)findViewById(R.id.progressBar1);
            ad.setLoadingIndicator(b);

            autoCompView.setAdapter(ad);
        }
}

Any ideas how to fix this? I am on android 4.3.

DarkLeafyGreen
  • 69,338
  • 131
  • 383
  • 601
  • What line of code is it crashing on? By the way, you are casting Context to Activity which is unsafe and will definitely cause crashes in some circumstances. I think you can replace `act.runOnUiThread` with `mHandler.post`, and create your Handler in the constructor. – Tenfour04 Nov 06 '13 at 19:19
  • Here is the log: http://pastebin.com/Hx7k28Rm – DarkLeafyGreen Nov 06 '13 at 19:23
  • @Tenfour04 activity and context, isn't it the same? – DarkLeafyGreen Nov 06 '13 at 19:25
  • They are not, otherwise you wouldn't have to cast. Activity is a subclass of context, so if someone tries to instantiate your adapter using `new PlacesAutoCompleteAdapter(getBaseContext())` (for example), they will get a ClassCastException when the filter runs. – Tenfour04 Nov 06 '13 at 19:59
  • The `Filter`'s `performFiltering()` method runs on a background thread and from that method you're changing the `resultList` on which your adapter is based. If you change that list and in that time the `ListView` access the adapter it will see that something has changed without its knowledge(and it will not be happy). Check this piece of code https://gist.github.com/luksprog/7382576 . – user Nov 09 '13 at 07:09
  • @Luksprog I refactored my code based on your comment and the problem disappeared. You can post this as a question. – DarkLeafyGreen Nov 09 '13 at 12:09

2 Answers2

37

The Filter's performFiltering() method runs on a background thread and from that method you're changing the resultList on which your adapter is based. If you change that list of data and in that time the ListView access the adapter it will see that something has changed without its knowledge(and it will not be happy). You should avoid using the resultList in the performFiltering method and simply create a new temporary list:

// in the performFiltering method which runs on a background thread:
@Override
protected FilterResults performFiltering(CharSequence constraint) {
     FilterResults filterResults = new FilterResults();
     ArrayList<String> queryResults;
     if (constraint != null && constraint.length() > 0) {
         queryResults = autocomplete(constraint);
     } else {
         queryResults = new ArrayList<String>(); // empty list/no suggestions showing if there's no valid constraint
     }
     filterResults.values = queryResults;
     filterResults.count = queryResults.size();
     return filterResults; // ## Heading ##
}

private List<String> autocomplete(String input) {
   // don't use the here the resultList List on which the adapter is based!
   // some custom code to get items from http connection
     ArrayList<String> queryResults = new ArrayList<String>(); // new list
     queryResults.add("Some String");
     return queryResults;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
     // update the data with the new set of suggestions
     resultList = (ArrayList<String>)results.values;
     if (results.count > 0) {
         notifyDataSetChanged();
     } else {
         notifyDataSetInvalidated();
     }
}
user
  • 86,916
  • 18
  • 197
  • 190
  • 2
    excellent guys! very very veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy helpful. Incredible ...... – nAkhmedov Nov 28 '14 at 05:30
  • @Luksprog.. sorry to bother and not trying to hijack this thread but could you take a look at this post.. its based on your solution here.. http://stackoverflow.com/questions/28568337/autocompletetextview-illegalstateexception-the-content-of-the-adapter-has-chang – irobotxx Feb 17 '15 at 18:23
  • The line `if (constraint != null || constraint.length() == 0) {` will cause a NPE, when constraint equals null. The if block should only check for constraint!=null – Purushothaman Ramraj Jul 25 '16 at 02:57
  • 1
    @luksprog short circuiting is the culprit here. when constraint is null, `constraint != null` becomes false, then the or operator will try to evaluate the constraint.length()==0, thereby causing the NPE. What we need is `if (constraint != null && constraint.length() > 0)`. Now short circuiting will protect against NPE – Purushothaman Ramraj Jul 25 '16 at 06:47
  • @PurushothamanRamraj Yeah, I've seen it now, at that time I stupidly mixed the two constraint conditions(not being null and not being empty). Good find, I wonder why nobody bothered to comment on this all this time. – user Jul 25 '16 at 07:14
  • I am having same problem can any one look into my [question](https://stackoverflow.com/questions/47034523/autocompletetextview-with-custom-adapter-filtering-not-working) ? – Moeez Oct 31 '17 at 12:21
0

Try this (just a guess):

@Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                if (constraint != null) {
                    ArrayList list = autocomplete(constraint.toString());
                    if (list != null) {
                        filterResults.values = list;
                        filterResults.count = list.size();
                    }
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    //change the underlying data immediately before notifying UI                        
                    resultList = (ArrayList)results.values; 
                    notifyDataSetChanged();
                }
                else {
                    notifyDataSetInvalidated();
                }
            }};
        return filter;
    }
David Ferenczy Rogožan
  • 23,966
  • 9
  • 79
  • 68
Tenfour04
  • 83,111
  • 11
  • 94
  • 154