Home > Back-end >  How can I make the options on the `Listview` navigate me to the correct fragments based on what is t
How can I make the options on the `Listview` navigate me to the correct fragments based on what is t

Time:09-06

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

Image Reference

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 Items, 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!

  • Related