How do I declare a menu inside of Android fragment? The method that I had used previously is now deprecated.
Originally:
override fun onCreateView(...): View {
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
this.menu = menu
inflater.inflate(R.menu.menu, this.menu)
}
From the Developer documentation, this can be achieved by the following:
/**
* Using the addMenuProvider() API directly in your Activity
**/
class ExampleActivity : ComponentActivity(R.layout.activity_example) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Add menu items without overriding methods in the Activity
addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// Add menu items here
menuInflater.inflate(R.menu.example_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Handle the menu selection
return true
}
})
}
}
/**
* Using the addMenuProvider() API in a Fragment
**/
class ExampleFragment : Fragment(R.layout.fragment_example) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// The usage of an interface lets you inject your own implementation
val menuHost: MenuHost = requireActivity()
// Add menu items without using the Fragment Menu APIs
// Note how we can tie the MenuProvider to the viewLifecycleOwner
// and an optional Lifecycle.State (here, RESUMED) to indicate when
// the menu should be visible
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// Add menu items here
menuInflater.inflate(R.menu.example_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Handle the menu selection
return when (menuItem.itemId) {
R.id.menu_clear -> {
// clearCompletedTasks()
true
}
R.id.menu_refresh -> {
// loadTasks(true)
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
Fragments setHasOptionsMenu deprecated, use setHasOptionsMenu
Expanding on what #joseph-wambura and #hammad-zafar-bawara said, you can also implement the interface in the fragment...
class MyFragment : Fragment(), MenuProvider {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Do stuff...
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.options, menu)
// Do stuff...
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Do stuff...
return false
}
}
In Kotlin, declaration for Activity, Fragment and PreferenceFragmentCompat
Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.main_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Handle the menu selection
return true
}
})
}
}
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// The usage of an interface lets you inject your own implementation
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// Add menu items here
menuInflater.inflate(R.menu.main_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Handle the menu selection
return when (menuItem.itemId) {
R.id.action_menu1 -> {
// todo menu1
true
}
R.id.action_menu2 -> {
// todo menu2
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
PreferenceFragmentCompat
val menuHost: MenuHost = requireHost() as MenuHost
//Same declaration with Fragment
Use MenuProvider interface
class FirstFragment : Fragment(), MenuProvider {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// Add menu items here
menuInflater.inflate(R.menu.second_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// Handle the menu selection
return when (menuItem.itemId) {
R.id.menu_clear -> {
// Do stuff...
true
}
R.id.menu_refresh -> {
// Do stuff...
true
}
else -> false
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
JAVA CODE
Option menu in Activity
addMenuProvider(new MenuProvider() {
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.bottom_nav_menu, menu);
// Add menu options here
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem) {
// Handle Menu Options Selection Here
return false;
}
});
Option menu in Fragment
requireActivity().addMenuProvider(new MenuProvider() {
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.bottom_nav_menu, menu);
// Add option Menu Here
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem) {
return false;
// Handle option Menu Here
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED);
If you're using Jetpack NavigationUI then you need to setSupportActionBar(toolbar), otherwise the menu will not be present.
this helps me on onCreateView Method:
requireActivity().addMenuProvider(new MenuProvider() {
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.bottom_nav_menu, menu);
// Add option Menu Here
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem) {
// Handle option Menu Here
return false;
}
}, getViewLifecycleOwner, Lifecycle.State.RESUMED);
I'm not sure why all answers recommend passing in Lifecycle.State.RESUME as the lifecycle state to the addMenuProvider call. This will make the menu disappear when the fragment is paused, which doesn't look great when the fragment is paused and still visible.
For example, showing a dialog as a result of tapping a menu item will make the menu disappear. It will re-appear when the dialog is dismissed.
For most cases, a better lifecycle state to pass in would be Lifecycle.State.CREATE, which will only remove the menu when the view is destroyed. This is also the default behaviour, so you can simply omit the lifecycle state.
in java for fragment i tried this . it works fine for me
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_example, container, false);
toolbar = (Toolbar)view.findViewById(R.id.toolbar_1);
((AppCompatActivity)getActivity()).setSupportActionBar(toolbar);
MenuHost menuHost = requireActivity();
menuHost.addMenuProvider(new MenuProvider() {
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater) {
menuInflater.inflate(R.menu.menu_search,menu);
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem) {
if (menuItem.getItemId() == R.id.search_friend){
Toast.makeText(getActivity(), "friends", Toast.LENGTH_SHORT).show();
return true;
}
else return false;
}
},getViewLifecycleOwner(), Lifecycle.State.RESUMED);
return view;
}
Related
After OnCreateOptionsMenu() marked as deprecated, I've managed to use new API from release notes https://developer.android.com/jetpack/androidx/releases/activity#1.4.0-alpha01
In my app user can switch Fragments via bottomNavigation.
As I understand docs, in each Fragment I've implemented MenuProvider(with or without Lifecycle, doesn't matter for result). But now in each Fragment user have all items from all menuInflaters.
There is the code of implementation
FRAGMENT A
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
SetMainParams();
fragment = inflater.inflate( R.layout.fragment_A, container, false );
addMenu();
return fragment;
}
private void addMenu()
{
MenuProvider menuProvider = new MenuProvider()
{
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater)
{
menuInflater.inflate(R.menu.menu_fragment_A, menu);
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem)
{
if( menuItem.getItemId() == R.id.filters_prev )
filtersPrevious();
else if( menuItem.getItemId() == R.id.filters )
showFilters();
else
filtersNext();
return false;
}
};
requireActivity().addMenuProvider(menuProvider, getViewLifecycleOwner(), Lifecycle.State.RESUMED);
}
FRAGMENT B
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
SetMainParams();
binding = FragmentBBinding.inflate(inflater, container, false);
fragment = binding.getRoot();
init();
addMenu();
return fragment;
}
private void addMenu()
{
MenuProvider menuProvider = new MenuProvider()
{
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater)
{
menuInflater.inflate(R.menu.menu_fragment_B, menu);
filtersMenu = menu.getItem(0);
}
#Override
public boolean onMenuItemSelected(#NonNull MenuItem menuItem)
{
if( menuItem.getItemId() == R.id.filters )
loadFilters();
return false;
}
};
requireActivity().addMenuProvider(menuProvider, getViewLifecycleOwner(), Lifecycle.State.RESUMED);
}
Switching from bottomNavigation
binding.bottomNav.setOnItemSelectedListener(item ->
{
int itemId = item.getItemId();
if( itemId == R.id.A )
{
fm.beginTransaction().hide(active_fragment).show(A_fragment).commit();
active_fragment = A_fragment;
setWithElevation(false);
}
else if( itemId == R.id.B )
{
fm.beginTransaction().hide(active_fragment).show(B_fragment).commit();
active_fragment = B_fragment;
setWithElevation(true);
}
active_fragment.startFragment();
active_fragment.setTitle();
return true;
});
fm.beginTransaction().add( R.id.fl_content, A_fragment, "A_fragment" ).hide(A_fragment).commit();
fm.beginTransaction().add( R.id.fl_content, B_fragment, "B_fragment" ).hide(B_fragment).commit();
Is there any ideas, why new API works like this, or maybe i've made a mistake. Thanks a lot for help :)
I also experienced this problem today. So not sure if you have intellisense, but look at some of the Menu interface methods. I used the hasVisibleItems() method in Kotlin to only inflate the menu if it does not exist. Java also has the method; your snippet augmented with conditional:
MenuProvider menuProvider = new MenuProvider()
{
#Override
public void onCreateMenu(#NonNull Menu menu, #NonNull MenuInflater menuInflater)
{
if (!menu.hasVisibleItem()) { // only inflate if no items
menuInflater.inflate(R.menu.menu_fragment_A, menu);
}
}
//...
Menu also has a bunch of other methods that may assist for specific items.
Also another good idea would be to use the MenuHost interface to keep track of your MenuProvider. I believe in Java; inside your Fragments; it would be along the lines of:
activityFragment = this.requireActivity();
Menuhost menuHost = (MenuHost) activityFragment;
menuHost.addMenuProvider(
menuProvider,
this.getViewLifecycleOwner(),
State.RESUMED
);
Menu providers can then be invalidated ect.
I've a similar problem. In my application, I'm using fragments and navigation. The problem I found is that during navigation, the fragment never call the onPause event. So all my fragment implements this code in onViewCreated method:
class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(HomeMenuProvider(R.id.nav_main, findNavController(), this, authViewModel, homeViewModel), viewLifecycleOwner, Lifecycle.State.RESUMED)
}
The HomeMenuProvider is so defined:
class HomeMenuProvider(private val currentNavId: Int) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
if ((navController.currentDestination?.id ?: -1L) == currentNavId) {
menu.clear()
menuInflater.inflate(R.menu.menu_main, menu)
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.menu_action_login -> {
...
true
}
else -> {
false
}
}
}
}
As you can see in my code, I add menu items only if the current navigation status is the one associated with the fragment. Otherwise, simply I do nothing.
To avoid menu persistence, before add items I clear the menu.
I hope this helps.
Previously I used onCreateOptionsMenu and onSupportNavigationUp.
Now I use MenuProvide. To check if navigation up was clicked I check id - "android.R.id.home". While it works, I'm not sure this is the right approach?
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_main, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
Log.d(TAG, "onMenuItemSelected: {${menuItem.itemId}}")
return when (menuItem.itemId) {
android.R.id.home -> {
navController.navigateUp(appBarConfiguration)
}
else -> {
true
}
}
}
})
}
In BottomNavigationView it is possible to set:
bottomNavigationView.setOnNavigationItemReselectedListener(item -> {
// do nothing on reselection
});
However for NavigationView this is not available. What is a good equivalent?
UPDATE
Thanks to #ande I implemented the following:
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (currentMenuItemId == item.getItemId()) {
navDrawer.close();
return true;
}
currentMenuItemId = item.getItemId();
NavigationUI.onNavDestinationSelected(item, navController);
navDrawer.close();
return true;
}
That works well for if I only navigate via the menu items. (Btw, I just implemented the Listener in my Activity and added it from there, no need for an extra class)
However, when I press the back button then I will be able to press the menu button for the current destination, as the menu item did not update on onBackPressed().
Update 2
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
currentMenuItemId = destination.getId();
}
});
This solved it!
May be you can do like this ( I did quickly so may be it could be better!!)
class ReSelectedListener(val navigationViewCallBack: NavigationViewCallBack) : NavigationView.OnNavigationItemSelectedListener {
var newItemSelected : MenuItem? = null
override fun onNavigationItemSelected(item: MenuItem): Boolean {
if(newItemSelected != null){
if(newItemSelected!!.itemId == item.itemId){
navigationViewCallBack.setOnNavigationItemReselectedListener(item)
}
}
newItemSelected = item
return true
}
}
interface NavigationViewCallBack {
fun setOnNavigationItemReselectedListener(item: MenuItem)
}
And after
class YourFragment :Fragment(), NavigationViewCallBack
and
navigationView.setNavigationItemSelectedListener(ReSelectedListener(this))
and implements callback method in YourFragment:
override fun setOnNavigationItemReselectedListener(item: MenuItem) {
TODO("Not yet implemented")
}
I have a SearchFragment
and here is my code of onCreateOptionMenu in SearchFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.search_menu, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as? SearchManager
searchView = searchItem?.actionView as? SearchView
searchView?.setSearchableInfo(searchManager?.getSearchableInfo(activity?.componentName))
queryTextListener = object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
return true
}
override fun onQueryTextSubmit(query: String): Boolean {
viewModel.loadSearchResults(query)
return true
}
}
searchView?.setOnQueryTextListener(queryTextListener)
super.onCreateOptionsMenu(menu, inflater)
}
I would like to know is it possible to have databinding for SearchView of menu so in the case I wouldn't like to have SearchView.OnQueryTextListener in Fragment and I can connect viewModel with menu and listen searchView changes in ViewModel
android:text="#={viewModel.query}"
Have you tried to move your listener to the viewModel directly?
class ViewModel: ViewModel() {
interface ViewModelListener {
fun onQueryTextChange(newText: String)
fun onQueryTextSubmit(newQuery: String)
}
var listener: ViewModelListener? = null
var query = ObservableField<String>()
val queryTextListener = object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
listener?.onQueryTextChange(newText: String)
return true
}
override fun onQueryTextSubmit(newQuery: String): Boolean {
listener?.onQueryTextSubmit(newQuery: String)
query.set(newQuery)
return true
}
}
}
And assign it in your onViewCreated like searchView?.setOnQueryTextListener(viewModel.queryTextListener)
Also you can add listener viewModel.listener = this
I have tried to add an setOnClickListener in my Fragment. But both methods i tried didnt work. There is no readction to my button press in the App.
class LoginFragment: Fragment(), View.OnClickListener {
//AuthVariable for global use
private lateinit var myAuth: FirebaseAuth
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var view = inflater.inflate(R.layout.content_login, container, false)
view?.login_button?.setOnClickListener(this)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onClick(p0: View?) {
Log.i("BUTTON123","TEST")
loginUser()
}
}
Activity:
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
//AuthVariable for global use
private lateinit var myAuth : FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
//Initializing FirebaseAuth Instance
myAuth = FirebaseAuth.getInstance()
var user = myAuth.currentUser
if(user != null){
updateUI(user)
}else{
val test = supportFragmentManager
val frag = LoginFragment()
test.beginTransaction().replace(R.id.placeholder,frag).addToBackStack(null).commit()
}
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
when (item.itemId) {
R.id.action_settings -> return true
else -> return super.onOptionsItemSelected(item)
}
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_addShop -> {
//Activity to Add your Shop
val addshopIntent = Intent(this,AddShopActivity::class.java)
startActivity(addshopIntent)
}
R.id.nav_searchShop -> {
//SearchFragment
val test = supportFragmentManager
val frag = test()
test.beginTransaction().replace(R.id.placeholder,frag).addToBackStack(null).commit()
}
R.id.nav_shopMap -> {
//MapActivity
val mapintent = Intent(this, ShopMap::class.java)
startActivity(mapintent)
}
R.id.nav_manage -> {
}
R.id.nav_share -> {
}
R.id.nav_logout -> {
myAuth.signOut()
updateUI(myAuth.currentUser)
}
}
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
Did you forget the onClick() method in LoginFragment?
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.content_login, container, false)
view.login_button.setOnClickListener {
Log.i("BUTTON123","TEST")
loginUser()
}
return view
}