I'm currently trying to enable one menu item I have on my Options Menu and disabling another and vice versa when it's pressed. I don't quite know how this is done and every other attempt I've tried hasn't been successful.
Here's how my app is currently set up. I'm using a single activity and multiple fragments. My MainFragment has the following within it to set up the Options menu:
class MainFragment: Fragment() {
...
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
...
setHasOptionsMenu(true)
(activity as AppCompatActivity).setSupportActionBar(binding.mfToolbar)
...
return binding.root
}
...
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
super.onOptionsItemSelected(item)
when(item.itemId) {
R.id.recycler_view_toggle_list -> {
// TODO: Implement what happens here with the RecyclerView
// Also disable this menu item and enable grid view item
}
R.id,recyclver_view_toggle_grid -> {
// TODO: Implement what happens here with the RecyclerView
// Also disable this menu item and enable list view item
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
menu_item.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.jre.projectcounter.ui.main.MainActivity">
<item
android:id="#+id/recycler_view_toggle"
android:icon="#drawable/ic_view_list"
android:title="#string/list_view"
android:visible="true"
app:showAsAction="ifRoom"/>
<item
android:id="#+id/recycler_view_toggle_grid"
android:icon="#drawable/ic_view_grid"
android:title="#string/grid_view"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="#+id/action_settings"
android:orderInCategory="100"
android:title="Settings"
app:showAsAction="never" />
</menu>
When I try to do the following within the when statement under R.id.recycler_view_toggle_list:
binding.mfToolbar.menu[R.id.recycler_view_toggle_list].isVisible = false
binding.mfToolbar.menu[R.id.recycler_view_toggle_grid].isVisible = true
I get the following exception:
java.lang.IndexOutOfBoundsException
What am I doing wrong or missing to get the options to switch between each other?
You need to use findItem if you're using the id of item. Because when you do menu[0] it expects index & not id.
So in your case it should be something like this:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
super.onOptionsItemSelected(item)
when(item.itemId) {
R.id.recycler_view_toggle -> {
binding.mfToolbar.menu.findItem(R.id.recycler_view_toggle).isVisible = false
binding.mfToolbar.menu.findItem(R.id.recycler_view_toggle_grid).isVisible = true
}
}
return true
}
Related
I have a simple android application, In my application, there is one image icon, and when I click the icon, I want to show context menu. My code is debugging but when I click the image button, nothing change, so context menu not work, I do not know where is the problem, Any idea will be appreciated.
MenuFragment:
class MenuFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_menu, container, false)
popupMenu()
}
private fun popupMenu() {
val popupMenu = PopupMenu(requireContext(), menu)
popupMenu.inflate(R.menu.menu)
popupMenu.setOnMenuItemClickListener {
when(it.itemId) {
R.id.menu_one -> {
Toast.makeText(requireContext(), "menu1", Toast.LENGTH_SHORT).show()
true
}
R.id.menu_two -> {
Toast.makeText(requireContext(), "menu2", Toast.LENGTH_SHORT).show()
true
}
else -> true
}
}
menu.setOnLongClickListener {
try {
val popup = PopupMenu::class.java.getDeclaredField("mPopup")
popup.isAccessible = true
val menu = popup.get(popupMenu)
menu.javaClass
.getDeclaredMethod("setForceShowIcon",Boolean::class.java)
.invoke(menu, true)
}catch(e: Exception) {
e.printStackTrace()
}finally {
popupMenu.show()
}
true
}
}
}
fragment_menu:
<ImageView
android:id="#+id/menu"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:src="#drawable/ic_menu"
android:text="5:33 PM"
android:textSize="12sp"
/>
menu:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/menu_one"
android:title="Menu1"
android:icon="#drawable/ic_menu_one"
/>
<item
android:id="#+id/menu_two"
android:title="Menu2"
android:icon="#drawable/ic_menu_two"
/>
It seems you want to show the menu as soon as possible, since the function invocation is after the return statement then it never happen.
You have to leverage the fragment lifecycle. Override onViewCreated and call it there
override... onViewCreated(...) {
super....
popUpMenu()
}
The method onViewCreated happens immediately after the view is created. If you need it when the view is ready to interact onStart. In both cases, make sure backward navigation doesn't show it again by mistake.
I have an in app contacts list screen (which is a fragment) and I want to add a searchView in my toolbar. This is my code:
Fragment code:
private lateinit var contactsToolbar: Toolbar
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_contacts, container, false)
setHasOptionsMenu(true);
contactsToolbar = rootView.findViewById(R.id.contacts_toolbar)
contactsToolbar.inflateMenu(R.menu.contacts_menu)
contactsToolbar.title = "Contacts"
.......
return rootView
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.contacts_menu, menu)
val search = menu.findItem(R.id.action_search)
val searchView = search.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
Log.d(TAG, "new query $query")
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
Log.d(TAG, "new text $newText")
return true
}
})
super.onCreateOptionsMenu(menu, inflater)
}
and this is my menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/action_search"
android:icon="#android:drawable/ic_menu_search"
app:showAsAction="always|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="Search"/>
</menu>
I can see that the searchView is displayed in the fragmanet as expected but the queryTextListener never gets called. Does anyone have any idea why this is happening?
Thank you in advance!
May be you can init the SearchView after onCreateOptionsMenu method?
I cannot sure it is true, but I met the same question as you, the text listener doesn't work.
because I init my SearchView like
val mySearchView by lazy{
...
}
Found a solution! Not sure if is the most elegant one but might help somebody in the future. Here is the fix:
In the onCreateView add this line of code:
yourActivity().setSupportActionBar(contactsToolbar)
This will make the searchView listener work but it might display the menu from the activity if you have one. To prevent this from happening and this line in onCreateOptionsMenu before inflating your menu.
menu.clear()
My application has one Activity and several fragments. Navigation Component was used for navigation and it is not very complicated. Also BottomNavigationComponent was used for bottom navigation.
There are three top level fragments which can be accessed via bottom navigation and there is an options menu at one of the top level fragments. Also this is not a complicated menu,too; there is just one item.
So, this is not a big deal and created menu like below.
notifications_menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/dismiss_all"
android:title="#string/dismiss_all"
android:orderInCategory="10"
app:showAsAction="ifRoom" />
</menu>
NotificationsFragment.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel =
ViewModelProvider.NewInstanceFactory().create(NotificationsViewModel::class.java)
setHasOptionsMenu(true)
(activity as MainActivity).supportActionBar?.setDisplayHomeAsUpEnabled(false)
(activity as MainActivity).supportActionBar?.setDisplayShowHomeEnabled(false)
//other stuff
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.notifications_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.dismiss_all) {
// there is no code here yet
return true
}
return super.onOptionsItemSelected(item);
}
And here is the result...
The problem is, as you can see above, when I tap Dismiss All button it navigates to the initial fragment. But I couldn't understand why?
After digging two hours I found that MainActivity's onOptionsItemSelected is causing this situation. Because as you can see it is handling all menu items and call onBackPressed event.
override fun onOptionsItemSelected(menuItem: MenuItem?): Boolean {
if (menuItem != null) {
onBackPressedDispatcher.onBackPressed()
}
return super.onOptionsItemSelected(menuItem)
}
Handling correctly this method solved my problem.
Here is the correct version of the code above.
override fun onOptionsItemSelected(menuItem: MenuItem?): Boolean {
val id = menuItem?.itemId
if (id == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onOptionsItemSelected(menuItem)
}
I got a fragment where I want to add a SearchView. I got it already in the Menu, its clickable and everything, but every guide I find to actually search something is either out of date or not exactly what I need.
This is my fragment
class ViewFragment : Fragment () {
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: ViewFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.view_fragment, container, false)
val application = requireNotNull(this.activity).application
setHasOptionsMenu(true)
binding.setLifecycleOwner(this)
viewFragmentViewModel.navigateToEdit.observe(this, Observer {translation ->
translation?.let {
this.findNavController().navigate(
ViewFragmentDirections
.actionViewFragmentToEditFragment(translation)
)
viewFragmentViewModel.onEditNavigated()
}
})
val adapter = TranslationAdapter(TranslationListener { translationID ->
viewFragmentViewModel.onTranslationClicked(translationID)
})
binding.translationList.adapter = adapter
viewFragmentViewModel.translations.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
inflater.inflate(R.menu.search_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
And this is my menu with the search
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<item
android:id="#+id/action_search"
android:icon="#android:drawable/ic_menu_search"
app:showAsAction="always|collapseActionView"
app:actionViewClass="android.widget.SearchView"
android:title="Search"/>
</menu>
Can anyone tell me how I can get set everything up so I can access the search string the user has entered and submit a new list to the RecycleView? Or just point me in the right direction!
Thank you in advance!
EDIT: Heres The update. I have an MutableLiveData Where i put the query and observe it so I can act on its observe function. I got a variable translationList in the ViewModel that I wanted to update.
class ViewFragment : Fragment (), SearchView.OnQueryTextListener {
private var searchText = MutableLiveData<String>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
val adapter = TranslationAdapter(TranslationListener { translationID ->
viewFragmentViewModel.onTranslationClicked(translationID)
})
binding.translationList.adapter = adapter
viewFragmentViewModel.translations.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
searchText.observe(this, Observer {searchString ->
searchString?.let {
if(searchString == ""){
adapter.submitList(viewFragmentViewModel.translations.value)
}
else{
adapter.submitList(viewFragmentViewModel.searchTranslations(searchString))
}
}
})
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater){
inflater.inflate(R.menu.search_menu, menu)
val menuItem = menu.findItem(R.id.action_search)
val searchView = menuItem.actionView as SearchView
searchView.setOnQueryTextListener(this)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onQueryTextChange(newText: String?): Boolean {
searchText.value = newText
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
}
For the closure, since I already had an list with all my objects from the database I wanted to search in, its easier to just go through that list with a filter.
Step - 1: Implements SearchView.OnQueryTextListener in your ViewFragment
class ViewFragment : Fragment(), SearchView.OnQueryTextListener {
...
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
//Do your operation here
return true
}
}
Step - 2: Get SearchView from menu and initialize with listener
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.search_menu, menu)
val meniItem = menu?.findItem(R.id.action_search)
val searchView = meniItem?.actionView as SearchView?
searchView?.setOnQueryTextListener(this)
super.onCreateOptionsMenu(menu, inflater)
}
Step - 3: Inside onQueryTextChange take your search query and do operation
Update: Change your implementation like below:
fun searchTranslations(searchString: String) : List<Translation>? {
return database.getTranslationsBySearch(searchString).value
}
And Dao
#Query("SELECT * FROM word_translation_table WHERE word_ger LIKE '% :string %' OR word_es LIKE '% :string %' ")
fun getTranslationsBySearch(string: String): LiveData<List<Translation>>
I have a navigation host activity that inflates 2 fragments, one the main host fragment and the event fragments, I need to show in the event fragment a menu action , but this menu action is also shown in the main fragment which I dont want to show
How do I show this menu only in the event fragment ?
MainActivity
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
setupActionBar(navController)
}
private fun setupActionBar(navController: NavController) {
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
return Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp()
|| super.onSupportNavigateUp()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
MenuInflater(this).inflate(R.menu.menu, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
NavigationUI.onNavDestinationSelected(item!!, Navigation.findNavController(this, R.id.nav_host_fragment))
return super.onOptionsItemSelected(item)
}
Menu
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="#+id/eventsFragment"
app:showAsAction="ifRoom|always"
android:title="¿ Where to buy ?" />
</menu>
I need to show this menu only in the eventsFragment
EventFragment
class EventsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_event, container, false)
}
But instead of just showing it just in the EventFragment it also shows it in the first fragment that MainActivity as hosts inflates
How can I just have this menu in my EventsFragment?
In MainActivity menuItem setVisible(false):
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
MenuInflater(this).inflate(R.menu.menu, menu)
val menuItem : MenuItem = menu.findItem(R.id.eventsFragment);
if (menuItem!= null)
menuItem.setVisible(false);
return super.onCreateOptionsMenu(menu)
}
In EventsFragment setvisibility of menuitem to true as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onPrepareOptionsMenu(menu: Menu) {
val menuItem = menu.findItem(R.id.eventsFragment)
if (menuItem != null)
menuItem.isVisible = true
}
You could try considering Inflating the menu from inside the fragment. ie. fragments onCreate() method and destroying it from onDestroy() function of the fragment