I am unable to navigate to the next fragment when there are characters typed in the searchview
. For instance if I were to type 'Dog' in the searchview
and select the Dog
option from the Listview
it would direct me back to the Homepage
.
However if nothing is in the searchview
, as in nothing is typed out in the searchview
the navigation works fine. For instance, clicking on the Dog option from the Listview
would navigate me to the Dog fragment.
I also can't seem to get the listview to collapse when not clicking on the searchview
.
I have attached an image as a reference to the problem I am facing.
In the code below I am using a default ArrayAdapter
Searchview.kt
//searchview
var user = arrayOf("Home","Dog","Cat","Hamster")
adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, user)
binding.categories.adapter = adapter
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (user.contains(query)) {
adapter.filter.filter(query)
} else {
Toast.makeText(this@MainActivity, "No Match found", Toast.LENGTH_LONG).show()
}
return false
}
override fun onQueryTextChange(newText: String): Boolean {
adapter.filter.filter(newText)
return false
}
})
val home = adapter.getItemId(0)
val cat = adapter.getItemId(1)
val hamster = adapter.getItemId(2)
val dog = adapter.getItemId(3)
binding.categories.setOnItemClickListener { parent, view, position, ID ->
when(ID){
home ->{navController.navigate(R.id.action_global_home2)
}
cat -> {navController.navigate(R.id.action_global_cat) }
hamster -> {navController.navigate(R.id.action_global_hamster)}
dog -> {navController.navigate(R.id.action_global_dog)
}
}
}
Searchview.xml
<androidx.drawerlayout.widget.DrawerLayout
.....
<androidx.appcompat.widget.SearchView
android:id="@ id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="16dp"
app:queryHint="Enter Search"
android:background="@drawable/searchview_background"
app:iconifiedByDefault="false"
app:queryBackground="@android:color/transparent"
>
</androidx.appcompat.widget.SearchView>
<!--Listview-->
<ListView
android:id="@ id/categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
tools:listitem="@layout/searchview_listitem"/>
.....
<androidx.drawerlayout.widget.DrawerLayout
CodePudding user response:
All getItemId
does for an ArrayAdapter
is return the item's position in the displayed list - you can check the source on this extremely nice site:
@Override public long getItemId(int position) { return position; }
It has use for other adapter implementations (e.g. where the ID is the database row the item corresponds to) but not in this case.
So because you're taking your original list, and storing their positions as home
, cat
etc., your click listener actually ends up equivalent to this:
when(position){
0 -> navController.navigate(R.id.action_global_home2)
1 -> navController.navigate(R.id.action_global_cat)
2 -> navController.navigate(R.id.action_global_hamster)
3 -> navController.navigate(R.id.action_global_dog)
}
And position
relates to their position in the displayed list, not in your data set. So when you filter on "dog", and Dog is the only item displayed, its position
is 0. Click that, and its ID (which is position remember) matches the first branch, the one that takes you to home.
So you can't really rely on the position
or default getItemId
implementation (which is the same thing) to identify your actual items. The easiest fix would be to grab the item itself (a String
in this case) and compare to that:
when(adapter.getItem(position)){
"Home" -> navController.navigate(R.id.action_global_home2)
"Cat" -> navController.navigate(R.id.action_global_cat)
"Hamster" -> navController.navigate(R.id.action_global_hamster)
"Dog" -> navController.navigate(R.id.action_global_dog)
}
Or if you really want to use indices, you could look up the item in your source array
val dataSet = user
// or you could create a list/array from inside the adapter with
// val dataSet = (0 until count).map(this::getItem)
when(dataSet.indexOf(adapter.getItem(position)){
0 -> navController.navigate(R.id.action_global_home2)
1 -> navController.navigate(R.id.action_global_cat)
2 -> navController.navigate(R.id.action_global_hamster)
3 -> navController.navigate(R.id.action_global_dog)
}
But this is kinda brittle - it requires you to define the order of your data in two different places, the ordering in the original array, and the ordering in your when
matcher. (You've already hit that problem - dog and hamster are in the wrong place in the when
!). Using the String values ensures you match the correct item - unless you make a typo, or change the text in the original array but not the matcher. They're separate, unconnected values, so there's no protection against that. It could break easily.
It would be better to associate your items with a label and a destination together, at the point where you define your data. Since ArrayAdapter
gets the string it displays by calling toString()
on whatever objects your array contains, you can put anything in there! You could make a simple class:
class Item(val name: String, val destination: Int) {
// we only want to display the name
override fun toString() = name
}
then use that to define your data all in the same place:
val items = arrayOf(
Item("Home", R.id.action_global_home2),
Item("Cat", R.id.action_global_cat),
Item("Hamster", R.id.action_global_hamster),
Item("Dog", R.id.action_global_dog)
)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
Then to handle your click, you just need to grab the Item
for the clicked position, and it has its navigation action right there in the data!
binding.categories.setOnItemClickListener { _, _, position, _ ->
val item = adapter.getItem(position)
item?.let { navController.navigate(it) }
// or item?.run(navController::navigate) - same thing, I just think it reads better!
}
You could do all this in an enum class
since it's hardcoded, but this way allows you to create arbitrary Item
s, load your data from a database or the internet etc, which is probably what you want to do in the end anyway. And you can put whatever data you want in Item
, whatever's useful!