On my working project we need to use SearchView
in a few Fragments
. The search bar itself have to be placed in Toolbar
and opened by clicking the search button at the end of the Toolbar
(see the image).
So, I implemented such behavior by:
androidx.appcompat.widget.SearchView
as root view;MenuProvider
interface in my fragment.Then, when I tried to test whether it works as expected I found that everything written inside onMenuItemSelected()
method is never being invoked. I mean when search button in toolbar is clicked, the search bar appears inside toolbar just as expected, but the system does not call my listener that handles the menu item selection.
I just do not understand why menu item selection callbacks are not invoked. I tried one more way to handle toolbar's menu and its items selection (e.g. placing the toolbar in my fragment, inflating the menu and then setting binding.toolbar.setOnMenuItemClickListener
with needed handler), but it has also led to the same ending - item selection callbacks just do not work at all.
I hope there is anyone who have ever faced the same problem and found a solution, because I have already wasted 3 days trying to get item selection callbacks working and now I feel stumped.
To reproduce the problem, I created a new project with bottom navigation bar template, added resources and implemented the MenuProvider
interface in one of the automatically generated fragments. To test whether the item selection callbacks are being invoked I used logger and none of my log messages appeared in logcat. Code examples are below:
HomeFragment.kt
class HomeFragment : Fragment(), MenuProvider {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private val queryConsumer: (String) -> Unit = { Log.i("HOME_FRAGMENT", "CONSUMED QUERY: $it") }
private var searchView: SearchView? = null
private val onBackPressed: OnBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
clearSearch()
closeSearch()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireActivity().onBackPressedDispatcher.addCallback(onBackPressed)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this)[HomeViewModel::class.java]
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textHome
homeViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
}
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val menuHost = requireActivity() as MenuHost
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.search_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.search_menu_item -> {
Log.i("HOME_FRAGMENT", "NOW IN SEARCH MENU ITEM BRANCH")
searchView = menuItem.actionView as? SearchView
searchView?.run {
queryHint = "hint"
maxWidth = Int.MAX_VALUE
setOnClickListener {
onSearchOpened()
}
setOnCloseListener {
onSearchClosed()
queryConsumer.invoke("")
return@setOnCloseListener false
}
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
queryConsumer.invoke(query)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
queryConsumer.invoke(newText)
return true
}
})
}
true
}
else -> {
Log.i("HOME_FRAGMENT", "NOW IN ELSE BRANCH")
false
}
}
fun clearSearch() {
searchView?.setQuery("", true)
}
fun closeSearch() {
searchView?.isIconified = true
}
fun onSearchClosed() {
onBackPressed.isEnabled = false
}
fun onSearchOpened() {
onBackPressed.isEnabled = true
}
}
layout_search.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.SearchView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.appcompat.widget.SearchView>
search_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item
android:id="@+id/search_menu_item"
android:contentDescription="@string/search_menu_title"
android:icon="?android:attr/actionModeWebSearchDrawable"
android:title="@string/search_menu_title"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
</menu>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
The problem was hiding in the menu .xml file...
I just randomly (from despair) changed the attribute app:showAsAction
value from always
to ifRoom|collapseActionView
and all callbacks (both onMenuItemSelected
from MenuProvider
and setOnMenuItemClickListener
from toolbar) started to work!