androidandroid-arrayadapterandroid-searchandroid-filter

SearchView fires only the second time when using a filter in ArrayAdapter


I have added in the Toolbar a SearchView to filter some items.

@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.menu, menu);
    MenuItem searchItem = menu.findItem(R.id.search);
    SearchView search = (SearchView) searchItem.getActionView();
    autoComplete = search.findViewById(androidx.appcompat.R.id.search_src_text);
}

In my MainActivity I get a the list of items an pass it to my adapter:

ItemsArrayAdapter adapter = new ItemsArrayAdapter(this, itemList);
autoComplete.setAdapter(adapter);

And this is my adapter class:

public class ItemsArrayAdapter extends ArrayAdapter<Item> {
    private List<Item> itemListFull;

    ItemsArrayAdapter(@NonNull Context context, @NonNull List<Item> items) {
        super(context, 0, items);
        itemListFull = new ArrayList<>(items);
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return itemFilter;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        Item item = getItem(position);
        if (convertView == null) {
            //Inflate view
        }
        return convertView;
    }

    private Filter itemFilter = new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults results = new FilterResults();
            List<Item> suggestionList = new ArrayList<>();
            if (constraint == null || constraint.length() == 0) {
                suggestionList.addAll(itemListFull);
            } else {
                String filterPattern = constraint.toString().toLowerCase().trim();
                for (Item item : itemListFull) {
                    if (item.name.toLowerCase().contains(filterPattern)) {
                        suggestionList.add(item);
                    }
                }
            }
            results.values = suggestionList;
            results.count = suggestionList.size();
            Log.d(TAG, "size: " + suggestionList.size());
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            List<Item> items = (List<Item>) results.values;
            if (items != null) {
                clear();
                addAll(items);
            }
            notifyDataSetChanged();
        }

        @Override
        public CharSequence convertResultToString(Object resultValue) {
            return ((Item) resultValue).name;
        }
    };
}

The number of items that exist in the list is 26. When I write a letter for example c from word car the number of items that is returned is 26, even though the words that contain c are only 11. When I type the second letter a the number of items that is returned is 1 (which is correct) and the item is correctly displayed in the view. How to make the SearchView fire and return the correct number of item from the first time? Thanks.


Solution

  • That search_src_text in SearchView is a descendant of AutoCompleteTextView, and it inherits most of its behavior from that class. AutoCompleteTextView has a default threshold of 2 characters. That is, it won't start filtering until you've input at least 2 characters, which explains why you were seeing the complete list when entering only one. We can fix that by lowering the threshold to 1.

    AutoCompleteTextView has the setThreshold() method, so we could simply call that with an argument of 1. However, as you noted, this causes a can only be called from within the same library group lint warning, because the specific subclass that the androidx SearchView uses is marked @hide in the source. That can easily be suppressed with @SuppressLint("RestrictedApi"), but if you'd rather not use a restricted API, there is another way.

    We can set that threshold via the android:completionThreshold XML attribute specified in a sub-style of the default AutoCompleteTextView style that we then set as the autoCompleteTextViewStyle in the Activity's theme. That is:

    <style name="AppTheme" parent="...">
        ...
        <item name="autoCompleteTextViewStyle">@style/LowThreshold.AutoCompleteTextView</item>
    </style>
    
    <style name="LowThreshold.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView">
        <item name="android:completionThreshold">1</item>
    </style>
    

    Note that the autoCompleteTextViewStyle attribute does not have the android prefix, because the actual class in SearchView is a subclass of AppCompatAutoCompleteTextView, which has its own default style attribute.