Search code examples
androidandroid-arrayadapterunsupportedoperationcustom-arrayadapter

Problem creating custom ArrayAdapter AndroidStudio


I am trying to create a custom ArrayAdapter that filters by "contains" instead of by "startswith". I have seen that there are similar questions, what's more, I made my custom ArrayAdapter out of those posts (like this one AutoCompleteTextView not completing words inside parentheses, or this other Change AutoCompleteTextView filter from "startsWith" to "Contains"?).

However I am getting an UnsupportedOperationException when calling clear() in publishResults. Does anyone knows why?

Here is my ArrayAdapter:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;

import com.example.osmdroid.R;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AutoSuggestAdapterNew<T> extends ArrayAdapter<String> {

    Context context;
    int resource, textViewResourceId;
    List<String> items, tempItems, suggestions;

    public AutoSuggestAdapterNew(Context context, int resource, int
            textViewResourceId, List<String> items) {
        super(context, resource, textViewResourceId, items);
        this.context = context;
        this.resource = resource;
        this.textViewResourceId = textViewResourceId;
        this.items = items;
        tempItems = new ArrayList<String>(items);// this makes the difference.
        suggestions = new ArrayList<String>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.autocompletestreets_layout, parent, false);
        }
        String name = items.get(position);
        if (name != null) {
            TextView lblName = (TextView) view.findViewById(R.id.autoCompleteStreets);
            if (lblName != null)
                lblName.setText(name);
        }
        return view;
    }

    @Override
    public Filter getFilter() {
        return nameFilter;
    }
    /**
     * Custom Filter implementation for custom suggestions we provide.
     */
    Filter nameFilter = new Filter() {
        @Override
        public CharSequence convertResultToString(Object resultValue) {
            String str = (String) resultValue;
            return str;
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            if (constraint != null) {

                suggestions.clear();
                FilterResults filterResults = new FilterResults();

                for (String name : tempItems) {
                    if (name.toLowerCase().contains(constraint.toString().toLowerCase())) {

                        suggestions.add(name);
                    }
                }

                /*String[] stringSuggestions = new String[suggestions.size()];
                stringSuggestions = suggestions.toArray(stringSuggestions);*/

                filterResults.values = suggestions;
                filterResults.count = suggestions.size();


                return filterResults;
            } else {
                return new FilterResults();
            }
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults
                results) {
            List<String> filterList = (ArrayList<String>) results.values;
            if (results != null && results.count > 0) {
                clear();
                for (String name : filterList) {
                    add(name);
                    notifyDataSetChanged();
                }
            }
        }
    };
}

EDIT: I discovered that I was passing an String array like Arrays.asList which returns a fixed-size list. Therefore I used new LinkedList(Arrays.asList()) and my error disappeared. However I am getting now a ConcurrentModificationException. These are my logs:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.osmdroid, PID: 20901
    java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.next(ArrayList.java:860)
        at com.example.osmdroid.Datos.AutoSuggestAdapterNew$1.publishResults(AutoSuggestAdapterNew.java:97)
        at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:284)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

Solution

  • from the documentation

    performFiltering(CharSequence constraint) Invoked in a worker thread to filter the data according to the constraint.

    so use suggestions in publishResults instead of performFiltering

    or create a new list in performFiltering like this

    @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            if (constraint != null) {
    
                final List<String> suggestions = new ArrayList<String>();
                FilterResults filterResults = new FilterResults();
    
                for (String name : tempItems) {
                    if (name.toLowerCase().contains(constraint.toString().toLowerCase())) {
    
                        suggestions.add(name);
                    }
                }
    
                /*String[] stringSuggestions = new String[suggestions.size()];
                stringSuggestions = suggestions.toArray(stringSuggestions);*/
    
                filterResults.values = suggestions;
                filterResults.count = suggestions.size();
    
    
                return filterResults;
            } else {
                return new FilterResults();
            }
        }