The Default ArrayAdapter only checks, if the possible suggestions starts with the current input (it uses .startsWith
. It doesn't use .contains
. However I would like it to use .contains
.
I already know about these two threads:
However, these don't really do what I want them to do. All of them have at least one of these problems:
1. The ArrayAdapter Class extends ThemedSpinnerAdapter
which requires API level 23
Downloading the sourcecode of the ArrayAdapter class and changing stuff in there, would be limited to API level 23 and up, because I can't find the ArrayAdapter for older versions.
2. When the list object gets updated from outside the adapter after the constructor has been called, the suggestions don't get updated
The best possible way I see to work around this is, overriding the notifyDataSetChanged
function. In that function I would then just copy the current Items in the list from the constructor into the temporary list that contains all the items that should be filtered through.
However, the notifyDataSetChanged
function also gets called from the Filter.publishResults
function, thereby causing the 3rd problem.
3. anything that was part of the list but didn't get suggested, will never appear again
To change what is shown, the list from the constructor has to be changed. However, this change has to be somehow undoable. The best way I know to do this would be to add a second list, that contains all the items from the first one, but the list object itself is a different one. But then this list has to be kept up to date somehow (That's the 2nd problem).
An implementation that suffers from the 3rd problem:
import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
class CustomStringArrayAdapter(
context: Context,
@LayoutRes resource: Int,
@IdRes textViewResourceId: Int = 0,
private val objects: List<String> = ArrayList()
) : ArrayAdapter<String>(context, resource, textViewResourceId, objects) {
inner class CustomStringFilter : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (!constraint.isNullOrEmpty()) {
val suggestions = mutableListOf<String>()
for (element in objects) {
if (element.contains(constraint, true)) {
suggestions.add(element)
}
}
filterResults.values = suggestions
filterResults.count = suggestions.size
}
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
if (results == null || results.count <= 0) {
notifyDataSetInvalidated()
return
}
val resultsValues = results.values
if (resultsValues !is List<*>) {
notifyDataSetInvalidated()
return
}
val result = mutableListOf<String>()
for (resultValue in resultsValues) {
if (resultValue is String) {
result.add(resultValue)
}
}
if (result.isEmpty()) {
notifyDataSetInvalidated()
} else {
clear()
addAll(result)
notifyDataSetChanged()
}
}
}
private lateinit var mFilter: Filter
override fun getFilter(): Filter {
if (!::mFilter.isInitialized) {
mFilter = CustomStringFilter()
}
return mFilter
}
}
An implementation that suffers from the 2nd problem:
import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
class CustomStringArrayAdapter(
context: Context,
@LayoutRes resource: Int,
@IdRes textViewResourceId: Int = 0,
private val objects: List<String> = ArrayList()
) : ArrayAdapter<String>(context, resource, textViewResourceId, objects) {
inner class CustomStringFilter : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (!constraint.isNullOrEmpty()) {
val completeList = tempObjects.toList()
filterResults.values = completeList
filterResults.count = completeList.size
} else {
val suggestions = mutableListOf<String>()
for (element in tempObjects) {
if (element.contains(constraint, true)) {
suggestions.add(element)
}
}
filterResults.values = suggestions
filterResults.count = suggestions.size
}
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
if (results == null || results.count <= 0) {
notifyDataSetInvalidated()
return
}
val resultsValues = results.values
if (resultsValues !is List<*>) {
notifyDataSetInvalidated()
return
}
val result = mutableListOf<String>()
for (resultValue in resultsValues) {
if (resultValue is String) {
result.add(resultValue)
}
}
if (result.isEmpty()) {
notifyDataSetInvalidated()
} else {
clear()
addAll(result)
notifyDataSetChanged()
}
}
}
private lateinit var mFilter: Filter
private val tempObjects = objects.toMutableList()
override fun getFilter(): Filter {
if (!::mFilter.isInitialized) {
mFilter = CustomStringFilter()
}
return mFilter
}
}
And another implementation that suffers from the 3rd problem:
import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
class CustomStringArrayAdapter(
context: Context,
@LayoutRes resource: Int,
@IdRes textViewResourceId: Int = 0,
private val objects: List<String> = ArrayList()
) : ArrayAdapter<String>(context, resource, textViewResourceId, objects) {
inner class CustomStringFilter : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if (!constraint.isNullOrEmpty()) {
val completeList = tempObjects.toList()
filterResults.values = completeList
filterResults.count = completeList.size
} else {
val suggestions = mutableListOf<String>()
for (element in tempObjects) {
if (element.contains(constraint, true)) {
suggestions.add(element)
}
}
filterResults.values = suggestions
filterResults.count = suggestions.size
}
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
if (results == null || results.count <= 0) {
notifyDataSetInvalidated()
return
}
val resultsValues = results.values
if (resultsValues !is List<*>) {
notifyDataSetInvalidated()
return
}
val result = mutableListOf<String>()
for (resultValue in resultsValues) {
if (resultValue is String) {
result.add(resultValue)
}
}
if (result.isEmpty()) {
notifyDataSetInvalidated()
} else {
clear()
addAll(result)
notifyDataSetChanged()
}
}
}
private lateinit var mFilter: Filter
private val tempObjects = objects.toMutableList()
override fun notifyDataSetChanged() {
tempObjects.clear()
tempObjects.addAll(objects)
super.notifyDataSetChanged()
}
override fun getFilter(): Filter {
if (!::mFilter.isInitialized) {
mFilter = CustomStringFilter()
}
return mFilter
}
}