I'm using Navigation fragment version 2.5.0. As release of version 2.4.0-alpha01, it's suppose to support back stack support for bottom navigation without writing any additional code. But my fragments are re-created everytime while I navigate with bottom navigation.
I'm using single graph, single activity Architecture.
Navigation dependencies
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
Nav-controller
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_container
) as NavHostFragment
navController = navHostFragment.navController
// Setup the bottom navigation view with navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
bottomNavigationView.setupWithNavController(navController)
// Setup the ActionBar with navController and 3 top level destinations
appBarConfiguration = AppBarConfiguration(
setOf(R.id.titleScreen, R.id.leaderboard, R.id.register)
)
setupActionBarWithNavController(navController, appBarConfiguration)
I am using the latest Navigation version 2.4.2.
I set up the bottom nav bar with the Navigation component as follow, the same way recommended by google:
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)
}
My menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/navigation_home"
android:icon="#drawable/ic_home_black_24dp"
android:title="#string/title_home" />
<item
android:id="#+id/navigation_dashboard"
android:icon="#drawable/ic_dashboard_black_24dp"
android:title="#string/title_dashboard"
xmlns:app="http://schemas.android.com/apk/res-auto" />
<item
android:id="#+id/navigation_notifications"
android:icon="#drawable/ic_notifications_black_24dp"
android:title="#string/title_notifications" />
However, when I navigate from one fragment to the other, its onDestroy() is called and when I navigate back to it is recreated.
This is the case for all the fragment in the BottomNavView except the startDestination. The onCreate() for the startDestination is called only once and when navigating away from it, only the onDestroyView() is called. I want this behavior for all the other fragments as well as I need to put code in the onCreate() method and want it to run once once per lifecycle of the app.
Support for multiple backstack arrive with Navigation 2.4.0 so I don't know what's wrong. when calling findNavController.navigate(...), the previous fragment is kept in the backstack and is not destroyed(), and as far as I know the BottomNavBar calls the same method so I can't figure out why each fragment is being created/destroyed upon each navigation.
Me navigating from : Start Fragment -> Dashboard Fragment -> Navigation Fragment -> Start Fragment
All the fragment except the Start Fragment is recreated.
Any help is appreciated.
For anyone looking for an answer to this question. Google replied that it is expected behaviour.
https://issuetracker.google.com/issues/190893266
Edit:
The original question was Is there a way to get a reference to a fragment that is displayed using a BottomNavigationView?. But I've figured some things out and realized I was asking the wrong question.
I'd like to get a reference to a fragment that is being displayed using a BottomNavigationView.
This is how my BottomNavigationView is being setup. It's in onCreate of an Activity.
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.bottom_nav_view_nav_host)
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_first_list,
R.id.navigation_second
))
setupActionBarWithNavController(navController, appBarConfiguration)
bottomNav.setupWithNavController(navController)
I've tried to get the fragment with bottomNav.findFragment<TheFragmentType>() it throws an exception.
I was asking the wrong question originally. I can just use the navController to call the correct navigation component in order to show the right fragment with a back stack.
val bundle = bundleOf("someId" to "theId")
navController.navigate(R.id.action_navigation_list_to_details, bundle)
I am currently working on an app that requires the navigation template from creation. I have been fine-tuning the look of the default navigation template for the app I need to work on. However, there's one thing I have been unable to find a solution to which is to change the text in the app bar. It keeps saying 'Home' but I need to put the title of my app there. I have tried several things such as: setTitle("App title here") in the mainActivity, and in the manifest file I have also tried changing the text in android:label to the App title but nothing I do from the solutions I find online seems to work.
Is there any way I can get around this and change the 'home' text into my App's title? Thanks in advance for any suggestions.
Code inside OnCreate as requested in a comment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTitle("Simple Dark Calculator")
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)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(setOf(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow), drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
Check the doc:
NavigationUI uses the destination labels from your navigation graph to keep the title of the top app bar up-to-date.
In your navigation graph change the android:label of the startDestination.
<navigation
app:startDestination="#+id/nav_home">
<fragment
android:id="#+id/nav_home"
android:label="#string/menu_home"
... >
</fragment>
You can also use the OnDestinationChangedListener to set the title after your setup method:
navController.addOnDestinationChangedListener { controller, destination, arguments ->
if (destination.id == R.id.nav_xxx){
supportActionBar?.title = "My Title"
}
//.....
}
Try to remove setTitle("Simple Dark Calculator") line and add code below after setSupportActionBar method:
getSupportActionBar().setTitle("Simple Dark Calculator");
To read more about setting up the app bar:
Set up the app bar
Try this in Manifest:
<activity android:name=".Youractivityname"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustResize"/>
I'm currently using the Android Architecture Component's Navigation, but I'm running into an issue with my Navigation Drawer. It shows the hamburger menu when at my starting destination, but other Fragments are showing the up arrow. I believe I've setup my navigation_graph incorrectly.
Here you can see my nav drawer, showing 2 items, Home and Settings. When in either of these Fragments, you should see the Hamburger icon.
However, when navigating to the Settings Fragment, it shows the Up arrow.
navigation.graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
app:startDestination="#id/nav_home">
<!-- Start at HomeFragment -->
<fragment
android:id="#+id/nav_home"
android:name=".HomeFragment"
android:label="#string/home">
<!-- Navigate to the Search -->
<action
android:id="#+id/action_nav_home_to_nav_search"
app:destination="#id/nav_search" />
</fragment>
<fragment
android:id="#+id/nav_settings"
android:name=".SettingsFragment"
android:label="#string/settings">
<!-- Navigate to the Search -->
<action
android:id="#+id/action_nav_settings_to_nav_search"
app:destination="#id/nav_search" />
</fragment>
<fragment
android:id="#+id/nav_search"
android:name=".SearchFragment"
android:label="#string/search" />
</navigation>
I feel like HomeFragment and SettingsFragment should be related somehow but I'm not sure how to define that.
main_drawer.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="#id/nav_home"
android:icon="#drawable/ic_home_white_24dp"
android:title="#string/home" />
<item
android:id="#id/nav_settings"
android:icon="#drawable/ic_settings_white_24dp"
android:title="#string/settings" />
</group>
</menu>
MainActivity
And then within MainActivity, I just set it up like this. I called setupActionBarWithNavController, but I also have to actually setup the nav drawer myself, and handle the onNavigationItemSelected.
private fun setupNavigation() {
navController = findNavController(R.id.mainNavigationFragment)
setupActionBarWithNavController(this, navController, drawer_layout)
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)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val current = navController.currentDestination.id
if (item.itemId != current) {
navController.navigate(item.itemId)
}
drawer_layout.closeDrawers()
return true
}
build.gradle
// Navigation
implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha04'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha04'
Thanks.
In newer alphas (I have 1.0.0-alpha07) they added possibility to define topLevelDestinationIds when calling AppBarConfiguration constructor.
So I setup my NavController like this
val navController = findNavController(R.id.nav_host_fragment)
val config = AppBarConfiguration(
setOf(
R.id.fistTopFragment,
R.id.secondTopFragment,
...
),
dr.drawerLayout
)
tb.setupWithNavController(navController, config)
Where dr is MaterialDrawer and tb of course Toolbar.
Then it behaves more like Gmail, at least for the ActionBarDrawerToggle, the back stack is still preserved. Since I must handle item selection in MaterialDrawer by myself, I'm going to reduce back stack actions using global navigation actions with inclusive popTo to the root fragment of the navigation graph and use something like a "Welcome screen" for now.
Another way around could be custom handling of the onBackPressed.
You must remove app:defaultNavHost="true" from your host fragment in activity layout first. Something like this
override fun onBackPressed() {
val navController = findNavController(R.id.nav_host_fragment)
if (navController.currentDestination == null
|| navController.currentDestination!!.id in setOf(
R.id.fistTopFragment,
R.id.secondTopFragment,
...
)
) {
super.onBackPressed()
} else {
navController.navigateUp()
}
}
Sorry about the code, I'm still learning Kotlin, so there is probably much nicer way of doing this.
I am afraid it is a feature of navigation component.
The action bar will also display the Up button when you are on a non-root destination and the drawer icon when on the root destination, automatically animating between them.
If you want, you can try to use setupWithNavController(NavigationView, NavController) and handle the toolbar yourself.
I made simple example for this issue. Solution is almost same as Almighty's answer. https://github.com/isaul32/android-sunflower
Create set of top level destinations at first
val topLevelDestinations = setOf(R.id.garden_fragment,
R.id.plant_list_fragment)
appBarConfiguration = AppBarConfiguration.Builder(topLevelDestinations)
.setDrawerLayout(drawerLayout)
.build()
and then override onSupportNavigateUp function like this
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
Back arrow appearing on tab fragments associated with BottomNavigationView is an intended behaviour. However haven't seen it being used "as is" even in famous apps (Instagram, Youtube which have bottom tabs). If you navigate to different bottom tabs in Youtube app for example and click device back option, you'll notice it goes to previous tab fragment and not exit app. So bottomnavigationview tab fragments are not root destinations here.
Want to bring to notice additional important issues which you might encounter as you move forward:
It does not allow for reuse of fragments in combination with BottomNavigationView
https://issuetracker.google.com/issues/110373186
If you have multiple activities the up button does not navigate up to the previous activity
https://issuetracker.google.com/issues/79993862
However there are several hooks you can use to customise the behaviour:
onBackPressed - to close drawer layout if open
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
Add navController.addOnNavigatedListener(..) and inside the listener customise HomeAsUpIndicator icon
Override onOptionsItemSelected to customise menu with id android.R.id.home action
Set custom fragment navigator to customise how your fragments are treated (replace or show/ hide)
navHostFragment = supportFragmentManager
.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? ?: return
val customNavigator = CustomFragmentNavigator(navHostFragment.requireContext(),
navHostFragment.childFragmentManager, navHostFragment.id)
navHostFragment.navController.navigatorProvider.addNavigator(customNavigator)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.main_nav_graph)
navHostFragment.navController.graph = graph
Remember navigation arch component is still in alpha, so use it wisely.
If you want, you can use NavController.OnNavigatedListener and use below code to set title.
#Override
public void onNavigated(#NonNull NavController controller, #NonNull NavDestination destination) {
mActivityBinding.toolbar.setTitle(destination.getLabel());
}
Remember to set label in your navigation graph.
I think your menu items should be like this:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#id/nav_home"
android:icon="#drawable/ic_home_white_24dp"
android:title="#string/home" />
<item
android:id="#id/nav_settings"
android:icon="#drawable/ic_settings_white_24dp"
android:title="#string/settings" />
</menu>
Hope this helps
Happy coding...
I completely agree with the sentiment here but it is a part of the library
setupActionBarWithNavController
Sets up the ActionBar returned by AppCompatActivity.getSupportActionBar() for use with a NavController.
By calling this method, the title in the action bar will automatically be updated when the destination changes (assuming there is a valid label).
The start destination of your navigation graph is considered the only top level destination. On the start destination of your navigation graph, the ActionBar will show the drawer icon if the given DrawerLayout is non null. On all other destinations, the ActionBar will show the Up button. Call navigateUp(NavController, DrawerLayout) to handle the Up button.
this to me is broken and not the desired result, so what i'm doing is still using it with the navigation view using
navController = Navigation.findNavController(this, R.id.nav_host);
NavigationUI.setupWithNavController(navigationView,navController);
and then having a listener to update the title and anything else i want changed like this
navController.addOnNavigatedListener((controller, destination) -> {
setToolbarColour(R.color.primary);
switch (destination.getId()){
case R.id.dashBoard :
setToolbarColour(android.R.color.transparent);
break;
case R.id.requests :
toolbar.setTitle(getString(R.string.request));
break;
}
});
I'm then creating a new navigation graph for each of these fragments, not great but i get my intended result