I'm using Android Navigation Component with a bottomNavigationView. I have configured the AppBar in my MainActivity so that a Navigate Up Button appears when the app is not on the start destination of the graph.
I have set SupportedActionBar and override onSupportNavigateUp like so:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.mainToolbar)
...
val controller = binding.bottomNavView.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container,
intent = intent
)
controller.observe(this, Observer { navController ->
appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf(
navController.graph.startDestination,
R.id.navigation_level_up_onboarding
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
})
currentController = controller
}
override fun onSupportNavigateUp(): Boolean {
return currentController?.value?.navigateUp() ?: false
}
In the navigation graph I have a destination ProfileSettingsFragment which needs to have menu options. I have set them like so:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
....
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.profile_settings_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
Menu options are working fine, but, the navigateUp button stops working for this fragment as soon as I call setHasOptionsMenu(true).
What am I doing wrong? What is the proper way to add menu options on a fragment while keeping navigateUp behaviour when using Navigation Component?
Since you are using a Toolbar instead of
setupActionBarWithNavController(navController, appBarConfiguration)
you can use
toolbar.setupWithNavController(navController, appBarConfiguration)
and you do not need to override the onSupportNavigateUp() method.
This works fine with me. While using actionbar in onOptionsItemSelected() add this check for the up button being clicked/pressed.
In kotlin:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home->
requireActivity().onBackPressed()
....//rest of your code
}
while in java:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home :
getActivity().onBackPressed();
break;
....//rest of your code
}
Related
I want to show navigation drawer icon instead of Back button on a certain fragment. I created the App with Navigation graph.
Let's say i have 2 fragments (LoginFragment and DashboardFragment) and one activity (MainActivity)
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var drawerLayout: DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
drawerLayout = binding.drawerLayout
val navController = this.findNavController(R.id.myNavHostFragment)
// prevent nav gesture if not on start destination
navController.addOnDestinationChangedListener { nc: NavController, nd: NavDestination, args: Bundle? ->
if (nd.id == nc.graph.startDestination) {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
} else {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
}
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
NavigationUI.setupWithNavController(binding.navView, navController)
}
// Set up the back button on action bar
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, drawerLayout)
}
}
LoginFragment.kt
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding:FragmentLoginBinding = DataBindingUtil.inflate(inflater,
R.layout.fragment_login, container, false)
// Hide the Action bar
(activity as AppCompatActivity).supportActionBar?.hide()
binding.loginButton.setOnClickListener {
//Some unimportant validation
}
return binding.root
}
}
DashboardFragment.kt
class DashboardFragment : Fragment() {
lateinit var binding : FragmentDashboardBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Binding object for this fragment and the layout
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_dashboard, container, false)
//Navigate to Product stock fragment when clicked
binding.productStockButton.setOnClickListener(Navigation.createNavigateOnClickListener(
R.id.action_dashboardFragment_to_productStockOutletList
))
//Navigate to Switching History fragment when clicked
binding.switchingHistoryButton.setOnClickListener(Navigation.createNavigateOnClickListener(
R.id.action_dashboardFragment_to_switchingHistoryFragment
))
//Navigate to Outlet List fragment for Outstanding Product when clicked
binding.outstandingOrderButton.setOnClickListener(Navigation.createNavigateOnClickListener(
R.id.action_dashboardFragment_to_outletListFragment
))
// Set action bar title to "Main Dashboard"
(activity as AppCompatActivity).supportActionBar?.title = "Main Dashboard"
// Declare that this fragment has menu
setHasOptionsMenu(true)
(activity as AppCompatActivity).supportActionBar?.show()
(activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(false)
//Return.... i don't know.
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater?.inflate(R.menu.nav_overflow_menu, menu)
}
}
And this is my Navigation graph (I set up LoginFragment as home)
I want to show the navigation drawer in the DashboardFragment instead of LoginFragment. (With LoginFragment still be the start of the graph). I already hide the Up button of the DashboardFragment
This is the current look of the dashboard. As you can see that the back button is already gone.
Is there anything i can do with it ? If there is something unclear let me know.
try this I hope that it will help you, this will hide actionBar on LoginFragment as it's the startDestination on graph and show it otherwise
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// prevent nav gesture if not on start destination
navController.addOnDestinationChangedListener { nc: NavController, nd: NavDestination, args: Bundle? ->
if (nd.id == nc.graph.startDestination) {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
supportActionBar?.hide()
} else {
supportActionBar?.show()
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
// here this ids are form fragment which should show navigation icon
appBarConfiguration = AppBarConfiguration(setOf(
R.id.nav_home,R.id.nav_gallery, R.id.nav_slideshow), drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
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 onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
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
I'm following single activity approach. I have navigation toolbar, whenever i go to other screens (fragments) instead of hamburger icon i will have back arrow.
What i want to achieve is, pop my current fragment using action on pressing toolbar back arrow.
I've tried
requireActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
NavHostFragment.findNavController(EventDetailsFragment.this)
.navigate(R.id.action_nav_event_details_to_nav_home);
}
});
But not getting the call over there, i checked by running app in debug mode.
in Activity oncreate:
navController = findNavController(R.id.my_nav_host)
//my_nav_host defined in activity xml file as id of fragment or FragmentContainerView
val appBarConfiguration = AppBarConfiguration(navController.graph)
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
and:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return true
}
then in your fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback: OnBackPressedCallback =
object : OnBackPressedCallback(true /* enabled by default */) {
override fun handleOnBackPressed() {
//do what you want here
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
Add this code in parent activity
Add in onCreate method
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Add this method also in parent activity
#Override
public boolean onSupportNavigateUp() {
return super.onSupportNavigateUp();
}
If you are using custom toolbar in your xml, you may consider using below approach
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
setSupportActionBar(activity_chat_toolbar)
activity_chat_toolbar.setNavigationOnClickListener {
onBackPressed() <-- custom toolbar's back press
}
val navHostFrag =
supportFragmentManager.findFragmentById(R.id.chat_fragment_container_view) as NavHostFragment
navController = navHostFrag.navController
navController.setGraph(R.navigation.chat_nav_graph, intent.extras)
setupActionBarWithNavController(navController)
}
// Function to check startDestination to finish() the parent activity
override fun onBackPressed() {
if(navController.graph.startDestination == navController.currentDestination?.id) {
finish()
} else {
super.onBackPressed()
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
Toolbar toolbar = findviewbyid(R.id.toolbar);
toolbar.setnavigationonclicklistener(new view.onclicklistener(){
code here
});
setsupportactionbar(toolbar);
I am making a simple note taking app, I have 2 fragments with navigation component, one fragment has a list of notes and the other is for editing or creating a new note.
In MainActivity I added
val navController = this.findNavController(R.id.host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
and then override onSupportNavigateUp()
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.host_fragment)
return navController.navigateUp()
}
In NoteEditFragment
requireActivity().onBackPressedDispatcher.addCallback(this) {
saveOrUpdateNote(noteId, note)
}
now it all works well when pressing the "back button" in the device, However onBackPressedDispatcher.addCallback() is note triggered when I press the "up button" the one on the top left of the screen.
My question is : How do I handle this up button from my NoteEditFragment?
Thanks in advance
Finally, I found the solution.
First In the activity onCreate method I had to connect the navigation like I did:
val navController = this.findNavController(R.id.host_fragment)
NavigationUI.setupActionBarWithNavController(this, navController)
Then still in MainActivity override onSupportNavigateUp() :
override fun onSupportNavigateUp(): Boolean
{
val navController = this.findNavController(R.id.host_fragment)
return navController.navigateUp()
}
Then In the Fragment onCreateView I had to enable option menu:
setHasOptionsMenu(true)
then in the fragment I overridden onOptionsItemSelected :
override fun onOptionsItemSelected(item: MenuItem): Boolean
{
// handle the up button here
return NavigationUI.onNavDestinationSelected(item!!,
view!!.findNavController())
|| super.onOptionsItemSelected(item)
}
Note: I think if you have more than one option menu, then I think you have to do a when (item) statement to check what option has been chosen.
Also if you want to handle the device back button then you can do like this in your fragment onCreateViewMethod :
requireActivity().onBackPressedDispatcher.addCallback(this)
{
// handle back button
// change this line to whatever way you chose to navigate back
findNavController().navigate(NoteEditFragmentDirections.actionNoteEditFragmentToNoteListFragment())
}
I think the accepted answer is a bit messy. So, i found a clean code. It is perfect for my use case as i don't need to do anything special when the device back button is pressed. Therefore, anyone here with the same requirements can follow my code.
In onCreate of MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph)
// Check if androidx.navigation.ui.NavigationUI.setupActionBarWithNavController is imported
// By default title in actionbar is used from the fragment label in navigation graph
// To use the app name, remove label else if you want to add customized label specify it there
setupActionBarWithNavController(this, navController, appBarConfiguration)
...
}
Again, in the MainActivity itself:
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.fragment)
// Check if androidx.Navigation.ui navigateUp is imported and used
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
I have added comments for better understanding. If you think the answer is helpful then please upvote. Thank You. Happy Development :)
EDIT 1:
No need to use supportActionBar.setDisplayHomeAsUpEnabled(true) in the fragments. AppBarConfiguration will take care of it.
if you use noActionBar themes and you want to use your own toolbar as an actionBar for the fragment, you can use this method
in the host activity
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.navHostFragment).navigateUp()
}
in the onCreateView of the fragment
(activity as AppCompatActivity).setSupportActionBar(binding.toolbar)
(activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
in very simple way you just need to set the supportNavigateUp on the Activity.
in MainActivity:
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
setupActionBarWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
in my case its worked
I have the following Activity
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: androidx.drawerlayout.widget.DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
drawerLayout = drawer_layout
val navController = Navigation.findNavController(this, R.id.fragment_main_navHost)
setSupportActionBar(toolbar)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
navView_main.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawerLayout,
Navigation.findNavController(this, R.id.fragment_main_navHost))
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
which as you can see is associated with navigation graph, and I am using a navigation drawer. When I am navigating through the items in the drawer I want to keep the hamburger icon, and only change it to up/back button when I click on an item within the fragment or popup for example and ensure that the behavior of the system reflects what the user expects based on the icon displayed. Is that possible
To control when the AppBar navigation up/back show the following need to be done
1- create AppBarConfiguration and pass to it the top level destination and drawerLayout
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.dest_one,
R.id.dest_two
),
drawerLayout
)
2- Tell the AppBar about the configration and navigation. this will help to show a title and show up arrow or drawer menu icon
setupActionBarWithNavController(navController, appBarConfig)
3- Finally override the onOptionsItemSelected and onSupportNavigateUp and the Navigation Component extension to inform the AppBar how to behave
override fun onOptionsItemSelected(item: MenuItem)= item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
override fun onSupportNavigateUp() = findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
Reference Google Code Lab Navigation Navigation Codelab
Follow this steps
1. Bind your NavigationView with NavigationUI
NavigationUI.setupWithNavController(nav_view, hostFragment.navController)
2. Bind ActionBar With NavController
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController)
3. Bind ActionBar and DrawerLayout With NavController
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController, drawer_layout)
4. override onSupportNavigateUp() in your activity
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawer_layout, hostFragment.navController)
|| super.onSupportNavigateUp()
}
Sample:
class NavActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
lateinit var hostFragment: NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nav)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
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)
hostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
NavigationUI.setupWithNavController(nav_view, hostFragment.navController)
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController)
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController, drawer_layout)
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawer_layout, hostFragment.navController) || super.onSupportNavigateUp()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.nav, 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.
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
Output
Home Fragment:
Fragment Two:
Fragment Tree:
So, I think that you can use the NavController.OnNavigatedListener to listen wich fragment will be shown, and then update de toolbar icon.
val navController = Navigation.findNavController(this, R.id.fragment_main_navHost)
navController.addOnNavigatedListener(contoller, destination -> {
if(destination.id == R.id.fragmentTwo){
// change the toolbar icon here
}
})
Sorry, I have no computer here, so I write this code without any IDE, this can have error. But take the idea.
Hope this help you.