Question

I have a list of key words (about 1000 words) and i set this to an ArrayAdapter to be handled by AutoCompleteTextView. The basic process works fine. The problem arise when i selected a long word (10 character above), then use the keyboard backspace button to remove the word (press and hold on the button), after removing like 5 characters the app crashes with the following error.

01-16 13:27:23.082: ERROR/AndroidRuntime(2874): FATAL EXCEPTION: main
        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.AutoCompleteTextView$DropDownListView) with Adapter(class com.hdm_i.dm.corp.android.muenchen.adapters.PoiAutoCompleteAdapter)]
        at android.widget.ListView.layoutChildren(ListView.java:1527)
        at android.widget.AbsListView.onLayout(AbsListView.java:1430)
        at android.view.View.layout(View.java:7228)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
        at android.view.View.layout(View.java:7228)
        at android.view.ViewRoot.performTraversals(ViewRoot.java:1145)
        at android.view.ViewRoot.handleMessage(ViewRoot.java:1865)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:130)
        at android.app.ActivityThread.main(ActivityThread.java:3687)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:507)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
        at dalvik.system.NativeStart.main(Native Method)  

Below is my code, did i do anything wrong ? Thanks in advance for your suggestions :-)

public class PoiAutoCompleteAdapter extends ArrayAdapter<SearchTextAutoSuggest> implements Filterable {

    private List<SearchTextAutoSuggest> searchTextAutoSuggestList = new ArrayList<SearchTextAutoSuggest>();
    private SearchTextAutoSuggest defaultSuggestion = new SearchTextAutoSuggest();

    private Handler uiThreadHandler;

    public PoiAutoCompleteAdapter(Context context, int viewResourceId, Handler uiThreadHandler) {
        super(context, viewResourceId);
        this.uiThreadHandler = uiThreadHandler;
        defaultSuggestion.setName(AppConstants.DEFAULT_SEARCH_STRING_NAME);
    }

    @Override
    public int getCount() {
        return searchTextAutoSuggestList.size();
    }

    @Override
    public SearchTextAutoSuggest getItem(int position) {
        return searchTextAutoSuggestList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                synchronized (filterResults) {
                    if (constraint != null) {
                        // Clear and Retrieve the autocomplete results.
                        searchTextAutoSuggestList.clear();
                        searchTextAutoSuggestList = getFilteredResults(constraint);

                        // Assign the data to the FilterResults
                        filterResults.values = searchTextAutoSuggestList;
                        filterResults.count = searchTextAutoSuggestList.size();
                    }

                    return filterResults;
                }
            }

            @Override
            protected void publishResults(CharSequence constraint, final FilterResults filterResults) {
                uiThreadHandler.post(new Runnable() {
                    public void run() {
                        synchronized (filterResults) {
                            if (filterResults != null && filterResults.count > 0) {
                                notifyDataSetChanged();
                            } else {
                                Logs.e("Tried to invalidate");
                                notifyDataSetInvalidated();
                            }
                        }
                    }
                });
            }
        };
        return filter;
    }

    private List<SearchTextAutoSuggest> getFilteredResults(CharSequence constraint) {
        List<SearchTextAutoSuggest> searchTextAutoSuggestList = AppContext.searchTextAutoSuggestList;
        List<SearchTextAutoSuggest> filteredSearchTextAutoSuggestList = new ArrayList<SearchTextAutoSuggest>();

        // Assign constraint as a default option into the list
        defaultSuggestion.setLabel(getContext().getString(R.string.general_default_search_str) + " \'" + constraint + "\'");
        filteredSearchTextAutoSuggestList.add(defaultSuggestion);

        for (int i = 0; i < searchTextAutoSuggestList.size(); i++) {
            if (searchTextAutoSuggestList.get(i).getLabel().toLowerCase().startsWith(constraint.toString().toLowerCase())) {
                filteredSearchTextAutoSuggestList.add(searchTextAutoSuggestList.get(i));
            }
        }

        return filteredSearchTextAutoSuggestList;
    }

}

Was it helpful?

Solution

because performFiltering executes in worker thread. And you assign your searchTextAutoSuggestList variable in this thread, but you have to change data of the adapter only in UI thread. Also publishResults method executes in UI thread so you don't need any Handlers here.

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    FilterResults filterResults = new FilterResults();
    synchronized (filterResults) {
        if (constraint != null) {
            // Clear and Retrieve the autocomplete results.
            List<SearchTextAutoSuggest> resultList = getFilteredResults(constraint);

            // Assign the data to the FilterResults
            filterResults.values = resultList;
            filterResults.count = resultList.size();
        }
        return filterResults;
    }
}

@Override
protected void publishResults(CharSequence constraint, final FilterResults filterResults) {
    if (filterResults != null && filterResults.count > 0) {
        searchTextAutoSuggestList.clear();
        searchTextAutoSuggestList = filterResults.values;
        notifyDataSetChanged();
    } else {
        Logs.e("Tried to invalidate");
        notifyDataSetInvalidated();
    }

}

OTHER TIPS

I was facing the same problem and after a lot of debugging and research I solved the problem by overriding notifyDataSetChanged() and evaluating the size of suggestionList. Snippet is as follows.

private int size = 0;

     @Override
        public void notifyDataSetChanged() {
             size = suggestionList.size();
            super.notifyDataSetChanged();
        }

…and then return the size in getCount():

@Override
    public int getCount() {

        return size; // Return the size of the suggestions list.
    }

…and the filter code goes here:

 private class CustomFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            suggestions.clear();
            FilterResults filterResults = new FilterResults();
            try {

                    if (originalList != null && constraint != null) { // Check if the Original List and Constraint aren't null.
                        try {
                            for (int i = 0; i < originalList.size(); i++) {
                                // if (originalList.get(i).toLowerCase().contains(constraint)) {
                                if (originalList.get(i).toLowerCase().contains(constraint.toString().toLowerCase())) {
                                    suggestionList.add(originalList.get(i)); // If TRUE add item in Suggestions.
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else {
                        notifyDataSetChanged();
                    }

            } catch (Exception e) {
                e.printStackTrace();
            }
           // Create new Filter Results and return this to publishResults;
            filterResults.values = suggestionList;
            filterResults.count = suggestionList.size();

            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
           if (results != null && results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }
    }

this for my xamarin.android fellows

i found a custom adapter/filter from xamarin examples but there is a bug in that example. the code below is the fixed . problematic code is commented

explanation: the example code was updating adapter in the PerformFilter . but the adapter's notifydatachanged called in the PublishResult . in that delay if user manages to delete text faster than the your filtering algroithm, then BOOOOOOOOOOOOOM!!!!

this is loosly related but: also cheesebaron example is doesnt work publish result input value filterresult.values is always null. that make me lose time.

   class SuggestionsFilter: Filter {

    string[] temp_matchitems_foradapter = new string[1];
    eArrayAdapter customAdapter;
    public SuggestionsFilter(eArrayAdapter adapter): base() {
     customAdapter = adapter;
    }
    protected override Filter.FilterResults PerformFiltering(Java.Lang.ICharSequence constraint) {
     FilterResults results = new FilterResults();
     if (constraint != null) {
      var searchFor = constraint.ToString();

      var matches = customAdapter.AllItems.Where(i =>  i.ToString().IndexOf(searchFor) >= 0 );


      #region  !!!!! FOCUS HEREEEEEEEEEE !!!!!      
      // WRONG----  dont update  adapter here. 
      //adapter.matchitems=  matches.ToArray();
      // RİGHT
      temp_matchitems_foradapter = matches.ToArray();
      #endregion 

      //this doesnt populate filtered view
      results.Values = FromArray(matches.Select(r => r.ToJavaObject()).ToArray());;
      results.Count = matches.Count();
     }
     return results;
    }

    protected override void PublishResults(Java.Lang.ICharSequence constraint, Filter.FilterResults results) 
    {
     //  update customAdapter.matchitems here  and notifychanges
     customAdapter.MatchItems = tempmathitems_foradapter;
     customAdapter.NotifyDataSetChanged();
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top