I am making an android app with Java that uses the SearchableSpinner from com.github.miteshpithadiya:SearchableSpinner:master.
I would like to modify it so it also can search for multiple words in the same string, in any order. For example, if I searched for the words "red grapefruit" in a list of foods, the Spinner would be reduced to these options:
Here is the xml:
<com.toptoche.searchablespinnerlibrary.SearchableSpinner android:id="@+id/spinner_view" android:layout_width="match_parent" android:layout_height="50dp" app:layout_constraintTop_toBottomOf="@+id/screenText" app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />
And in MainActivity, I added a custom ArrayAdapter to the SearchableSpinner (I put "getApplicationContext()" instead of "this" because it's in another block):
SearchableSpinner searchableSpinner = findViewById(R.id.spinner_view); String[] dropDownArray = foodsMap.values().toArray(new String[0]); ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_spinner_item, dropDownArray) { @Override public Filter getFilter() { return new android.widget.Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); List<String> suggestions = new ArrayList<>(); if (constraint == null || constraint.length() == 0) { suggestions.addAll(items); } else { String[] queryWords = constraint.toString().toLowerCase().split(" "); for (String item : items) { String lowerItem = item.toLowerCase(); boolean allWordsMatch = true; for (String word : queryWords) { if (!lowerItem.contains(word)) { allWordsMatch = false; break; } } if (allWordsMatch) { suggestions.add(item); } } } results.values = suggestions; results.count = suggestions.size(); return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { clear(); if (results.count > 0) { addAll((List<String>) results.values); notifyDataSetChanged(); } } }; } }; adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); searchableSpinner.setAdapter(adapter);
But it does not work. It completely empties when I enter "red" followed by a space.
Is there something else I need to override or add?
Thw way you are doing it is almost right, there is 1 or 2 issues only due to which it is not working properly.
Here when the user types a trailing space like example ("red ") then split(" ") can create empty tokens meaning so your all words must match loop rejects everything. You can fix it by trim() and also splitting on whitespace "\\s+" also & ignore empty words also.
Do not filter from the adapter’s current items which you are keep clearing or adding.. Try to always filter from a separate original list that should never change.
example how to do this will match multiple words in any order & will not empty out just because the user typed a space, try if this workss
SearchableSpinner searchableSpinner = findViewById(R.id.spinner_view);
final List<String> allItems = new ArrayList<>(foodsMap.values()); // master list (never mutate)
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_item,
new ArrayList<>(allItems)
) {
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<String> out = new ArrayList<>();
String q = constraint == null ? "" : constraint.toString()
.toLowerCase(java.util.Locale.ROOT)
.trim();
if (q.isEmpty()) {
out.addAll(allItems);
} else {
String[] words = q.split("\\s+"); // handles trailing/multiple spaces
for (String item : allItems) {
String s = item.toLowerCase(java.util.Locale.ROOT);
boolean ok = true;
for (String w : words) {
if (w.isEmpty()) continue;
if (!s.contains(w)) { ok = false; break; }
}
if (ok) out.add(item);
}
}
results.values = out;
results.count = out.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
//noinspection unchecked
addAll((List<String>) results.values);
notifyDataSetChanged();
}
};
}
};
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
searchableSpinner.setAdapter(adapter);