Android Navigation Architecture Component - Nav Drawer Icons - android

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

Related

Navigation in Bottom Navigaiton View creates/destroys the fragment Navigation 2.4.2

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

How to go back to the last fragment in Android using a BottomNavigationBar

I use a One-Activity-Multiple-Fragments approach and the JetPackNavigation with a NavGraph. I have added a BottomNavigationBar to navigate to 2 Fragments which works as it should. Now I want to put a backbuttom into the BottomNavigationBar with the intensions that if someone presses it, it should go back to the very last fragment visited. I want to know if something like this is possible and if I have to set the connections in the NavGraph from all Fragments to the other Fragments such that it can navigate back? The problem is that my NavGraph is quite large and contains many fragments.
Here you see a screenshot from the NavGraph where I indicated the 2 Fragments for the Navigation in the BottomNavigationBar (which work quite well).
Further, here you see the XML code for the BottomNavigationBar.
<?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/Back_BottomNavigation"
android:icon = "#drawable/ic_baseline_arrow_left_24"
android:title = "Back" />
<item
android:id="#+id/FR_LanguageSelection"
android:icon = "#drawable/ic_add_circle_full"
android:title = "#string/Language" />
<item
android:id="#+id/Fragment1"
android:icon = "#drawable/ic_add_circle_full"
android:title = "Fragment1" />
</menu>
Do you know if I can implement the back-buttom such that it will go back to the last displayed Fragment? I'd appreciate every comment.
Does nobody have an idea how I can do this? I read quite often that the Jetpack Navigation is good for handling the backstack. Is this true and how can I do this?
Update: I inserted, as adviced in one answer, a Listener for the BottomNavigationView into the MainActivity (which hosts all the Fragments using the NavHostFragment). Now the back navigation works as it should. However, the normal Jetpack navigation using the other Bottoms of the BottonNavigationBar does not work anymore. When I click on the other bottoms, nothing happens (before adding the Listener in the Main Activity it was working perfectly). Do you have any idea, how I can solve this problem? Here is the code of the newly added Listener in the onCreateMethod of the MainActivity class:
//These commands were there before
final NavController navController = Navigation.findNavController(this,
R.id.navHostfragment);
NavigationUI.setupWithNavController(binding.bottomNavigation, navController);
//These are the new commands
binding.bottomNavigation.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.Back_BottomNavigation:
navController.navigateUp();
}
return true;
}
});
Any ideas about how to tackle this problem?
documentation
set the behaivor you want in BottomNavigationView's OnNavigationItemSelectedListener
yourBottomNavigationView.setOnNavigationItemReselectedListener(object : OnNavigationItemSelectedListener{
#override fun onNavigationItemSelected (MenuItem item){
when(item){
//insert your condition to match your requirement
}
}
})

How to save state of Bottom Navigation Fragment - Android Navigation Component With a Single Nav Graph

How do I save the state of each bottom navigation fragment while using Android Navigation Component JetPack.
I know there is a way to do it using an Navigation Extension provided by the Android Team - Navigation Extension. - While it works, it requires you to create multiple nav_graph for each fragment and also does not have the back stack I want. Also, switching between fragment seems slow using their approach.
How do I do save the state using a single nav_graph and maintain each back stack.
I am following this tutorial and its working but not saving the state of each fragment. Each instance of the fragment is created on Click of the bottom nav item. - Bottom Nav Tutorial Like Instagram And Youtube
activity_home.xml
<fragment //I get a warning here, when I change to FragmentContainerView, app crashes//
android:id="#+id/nav_host_fragment_2"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#+id/bottom_navigation"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph_2" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:itemTextColor="#color/white"
app:itemIconSize = "30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_navigation"/>
menu/bottom_navigation.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/feedRandomFragment"
android:icon="#drawable/home_bottom_nav_selector"
android:title="#string/home"
android:menuCategory="secondary"
/>
<item
android:id="#+id/exploreAndSearchFragment"
android:icon="#drawable/explore_bottom_nav_selector"
android:title="#string/global_explore"
android:menuCategory="secondary"
/>
<item
android:id="#+id/uploadChooseFragment"
android:icon="#drawable/ic_upload"
android:title="#string/upload"
android:menuCategory="secondary"
/>
<item
android:id="#+id/allChallengesFragment"
android:icon="#drawable/challenges_bottom_nav_selector"
android:title="#string/challenges"
android:menuCategory="secondary"
/>
<item
android:id="#+id/profileCurrentUserFragment"
android:icon="#drawable/profile_bottom_nav_selector"
android:title="#string/profile"
android:menuCategory="secondary"
/>
HomeActivity.kt
if(savedInstanceState==null){
setUpBottomNavigationBarBase()
}
private fun setUpBottomNavigationBarBase(){
binding.bottomNavigation.setupWithNavController(Navigation.findNavController(this,
R.id.nav_host_fragment_2))
binding.bottomNavigation.setOnNavigationItemSelectedListener {item ->
onNavDestinationSelected(item, Navigation.findNavController(this, R.id.nav_host_fragment_2))
}
binding.bottomNavigation.itemIconTintList = null
binding.bottomNavigation.setOnNavigationItemReselectedListener {
//do something
}
}
According to the Tutorial, to Maintain backstack, we have to extend all bottom nav fragments from a util class BaseBottomTabFragment which I did and works well.
BaseBottomFragment
open class BaseBottomTabFragment : Fragment() {
var isNavigated = false
fun navigateWithAction(action: NavDirections) {
isNavigated = true
findNavController().navigate(action)
}
fun navigate(resId: Int) {
isNavigated = true
findNavController().navigate(resId)
}
override fun onDestroyView() {
super.onDestroyView()
if (!isNavigated)
requireActivity().onBackPressedDispatcher.addCallback(this) {
val navController = findNavController()
if (navController.currentBackStackEntry?.destination?.id != null) {
findNavController().popBackStackAllInstances(
navController.currentBackStackEntry?.destination?.id!!,
true
)
} else
navController.popBackStack()
}
}
private fun NavController.popBackStackAllInstances(destination: Int, inclusive: Boolean): Boolean {
var popped: Boolean
while (true) {
popped = popBackStack(destination, inclusive)
if (!popped) {
break
}
}
return popped
}
}
So, All my bottom tab fragments extends from that util class - BaseBottomTabFragment like this :
class ExploreAndSearchFragment : BaseBottomTabFragment()
Also, accoriding to the tutorial, to maintain the state of fragment and avoid recreation, each fragment has to have a unique ID which I also did - Sadly, this does not stop the fragment from recreating onClick.
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.main.bottomnav.home.view.FeedRandomFragment"
android:fillViewport="true"
android:background="#color/white"
android:id="#+id/homeId">
I set a unique id to All fragment, but it didnt work. Please Help me!
As I had a similar problem, I copied the Navigation Advanced Sample from their GitHub page and started troubleshooting.
My goal was to use multiple back stacks while having an Instagram-like bottom navigation bar. So I wanted to combine MAD Skills' tutorial Navigation: Multiple back stacks with
Furkan Aşkın's Instagram-like back stack guide.
As I implemented both components, the project compiled perfectly but multiple back stacks didn't seem to work as the states of the bottom tabs weren't saved. I double-checked the version to be 2.4.0-alpha01 or newer.
What ended up being the problem was secondary menuCategory defined in menu items:
android:menuCategory="secondary"
This was probably overwriting the multiple back stacks expected behaviour and upon deleting the lines, multiple back stacks work perfectly. Also, redundant back stacks are removed while navigating in different bottom navigation top level fragments.

How can i change the text in the app bar?

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"/>

How to change start destination of a navigation graph programmatically?

Basically, I have the following navigation graph:
I want to change my starting point in navigation graph to fragment 2 right after reaching it (in order to prevent going back to fragment 1 when pressing back button - like with the splash screen).
This is my code:
navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);
But, obviously it's not working and it gets back to fragment 1 after pressing back button.
Am I doing it wrong?
Is there any other solution?
UPDATE:
When you have nav graph like this:
<fragment
android:id="#+id/firstFragment"
android:name="com.appname.package.FirstFragment" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.appname.package.SecondFragment"/>
And you want to navigate to the second fragment and make it root of your graph, specify the next NavOptions:
NavOptions navOptions = new NavOptions.Builder()
.setPopUpTo(R.id.firstFragment, true)
.build();
And use them for the navigation:
Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions);
setPopUpTo(int destinationId, boolean inclusive) - Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.
destinationId - The destination to pop up to, clearing all intervening destinations.
inclusive - true to also pop the given destination from the back stack.
ALTERNATIVE:
<fragment
android:id="#+id/firstFragment"
android:name="com.appname.package.FirstFragment" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.appname.package.SecondFragment"/>
And then on your code:
findNavController(fragment).navigate(
FirstFragmentDirections.actionFirstFragmentToSecondFragment())
Old answer
Deprecated: The clearTask attribute for actions and the associated API in NavOptions has been deprecated.
Source: https://developer.android.com/jetpack/docs/release-notes
If you want to change your root fragment to fragment 2 (e.g. after pressing back button on fragment 2 you will exit the app), you should put the next attribute to your action or destination:
app:clearTask="true"
Practically it looks in a next way:
<fragment
android:id="#+id/firstFragment"
android:name="com.appname.package.FirstFragment"
android:label="fragment_first" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"
app:clearTask="true" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.appname.package.SecondFragment"
android:label="fragment_second"/>
I've added app:clearTask="true" to action.
Now when you perform navigation from fragment 1 to fragment 2 use the next code:
Navigation.findNavController(view)
.navigate(R.id.action_firstFragment_to_secondFragment);
In MainActivity.kt
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.booking_navigation)
if (isTrue){
graph.startDestination = R.id.DetailsFragment
}else {
graph.startDestination = R.id.OtherDetailsFragment
}
val navController = navHostFragment.navController
navController.setGraph(graph, intent.extras)
Remove startDestination from nav_graph.xml
?xml version="1.0" encoding="utf-8"?>
<!-- app:startDestination="#id/oneFragment" -->
<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"
android:id="#+id/navigation_main">
<fragment
android:id="#+id/DetailFragment"
android:name="DetailFragment"
android:label="fragment_detail"
tools:layout="#layout/fragment_detail"/>
<fragment
android:id="#+id/OtherDetailFragment"
android:name="OtherDetailFragment"
android:label="fragment_other_detail"
tools:layout="#layout/fragment_other_detail"/>
</navigation>
I found a solution for this, but it's ugly. I guess this it to be expected with an alpha library, but I hope Google looks into simplifying/fixing this as this is a pretty popular navigation pattern.
Alexey's solution did not work for me. My problem was that I have up arrows showing on my Actionbar by using:
NavigationUI.setupActionBarWithNavController(this, navController)
If I did as Alexey suggests above, my new start fragment still had a arrow pointing to my initial start fragment. If I pressed that up arrow my app would sort-of restart, transitioning to itself (the new start fragment)
Here is the code needed to get to what I wanted which was:
Fragment #1 is where my application initially starts
I can do an Auth check in Fragment #1 and then programmatically change the start to fragment #2.
Once in Fragment #2 there is no up arrow and pressing the back button does not take you to Fragment #1.
Here is the code that accomplishes this. In my Activity's onCreate:
// Setup the toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(false)
// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController
.navInflater.inflate(R.navigation.nav_graph)
graph.startDestination = R.id.welcomeFragment
// This seems to be a magical command. Not sure why it's needed :(
navHost.navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navHost.navController)
and also:
fun makeHomeStart(){
graph.startDestination = R.id.homeFragment
}
Then in Fragment #1's onActivityCreated, per Alexey's suggestion:
override fun onActivityCreated(savedInstanceState: Bundle?) {
...
// Check for user authentication
if(sharedViewModel.isUserAuthenticated()) {
(activity as MainActivity).makeHomeStart() //<---- THIS is the key
val navOptions = NavOptions.Builder()
.setPopUpTo(R.id.welcomeFragment, true)
.build()
navController.navigate(R.id.action_welcomeFragment_to_homeFragment,null,navOptions)
} else {
navController.navigate(R.id.action_welcomeFragment_to_loginFragment)
}
}
The key code is:
(activity as MainActivity).makeHomeStart() which just runs a method in the activity that changes the graphs startDestination. I could clean this up and turn it into an interface, but I'll wait for Google and hope they improve this whole process. The method 'setPopUpTo' seems poorly named to me and it's not intuitive that your naming the fragment that is getting cut out of the graph. It's also strange to me that they're making these changes in navOptions. I would think navOptions would only relate to the navigation action they're connected to.
And I don't even know what navHost.navController.graph = graph does, but without it the up arrows return. :(
I'm using Navigation 1.0.0-alpha06.
You can also try the followings.
val navController = findNavController(R.id.nav_host_fragment)
if (condition) {
navController.setGraph(R.navigation.nav_graph_first)
} else {
navController.setGraph(R.navigation.nav_graph_second)
}
Instead of trying to pop start destination or navigate manually to target destination, it would be better to have another navigation graph with different workflow.
This would be even better for the case when you want completely different navigation flow conditionally.
You don't really need to pop the Splash Fragment. It can remain there for the rest of your App life. What you should do is from the Splash Screen determine which next Screen to Show.
In the picture above you can ask in the Splash Screen State if there is a saved LoginToken. In case is empty then you navigate to the Login Screen.
Once the Login Screen is done, then you analyze the result save the Token and navigate to your Next Fragment Home Screen.
When the Back Button is Pressed in the Home Screen, you will send back a Result message to the Splash Screen that indicates it to finish the App.
Bellow code may help:
val nextDestination = if (loginSuccess) {
R.id.action_Dashboard
} else {
R.id.action_NotAuthorized
}
val options = NavOptions.Builder()
.setPopUpTo(R.id.loginParentFragment, true)
.build()
findNavController().navigate(nextDestination, null, options)
For those who have a navigation xml file with similar content to this:
<?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"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/nav_home">
<fragment
android:id="#+id/nav_home"
android:name="HomeFragment"
android:label="#string/menu_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/nav_users"
android:name="UsersFragment"
android:label="#string/users"
tools:layout="#layout/fragment_users" />
<fragment
android:id="#+id/nav_settings"
android:name="SettingsFragment"
android:label="#string/settings"
tools:layout="#layout/fragment_settings" />
</navigation>
suppose current fragment opened is the home fragment and you want to navigate to users fragment, for that just call in the setOnClickListener of the element that you want to navigate to the navigate method from the nav controller similar to this code:
yourElement.setOnClickListener {
view.findNavController().navigate(R.id.nav_users)
}
that will make the app navigate to that other fragment and will also handle the title in the toolbar.
Okay, after messing with this for a bit I found a solution that worked for me that didn't require a ton of work.
It appears two things MUST be in place for it function as if your secondFragment is your start destination.
use the ALTERNATIVE option in the accepted post
<fragment
android:id="#+id/firstFragment"
android:name="com.appname.package.FirstFragment" >
<action
android:id="#+id/action_firstFragment_to_secondFragment"
app:destination="#id/secondFragment"
app:popUpTo="#+id/firstFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.appname.package.SecondFragment"/>
The above will remove firstFragment from the stack and inflate secondFragment when moving. The app cannot step back to firstFragment anymore BUT your left with secondFragment showing a back arrow as #szaske stated.
This is what made the difference. I previously defined my AppBarConfig using the NavigationController.graph like so
// Old code
val controller by lazy { findNavController(R.id.nav_host_fragment) }
val appBarConfig by lazy { AppBarConfiguration(controller.graph) }
Updating it to define a set of top-level destinations rectified the issue of showing the back arrow on secondFragment instead of a hamburger menu icon.
// secondFragment will now show hamburger menu instead of back arrow.
val appBarConfig by lazy { AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment)) }
Setting the start destination may or may not have negative implications in your project so do it as needed however in this example we do not need to do so. If it makes you warm and fuzzy to ensure that your graph has the correct start fragment defined, you can do it like so.
controller.graph.startDestination = R.id.secondFragment
Note: Setting this does not prevent the back arrow from occurring in secondFragment and from what I have found seems to have no effect on navigation.
I tried to modify code in startDestination.
It works well, but It does not keep the activity, the Navigation component does not restore fragment stack.
I resolved this problem with a dummy startDestination
startDestination is EmptyFragment(just a dummy)
EmptyFragment to FirstFragment action require popUpTo=EmptyFragment and popUpToInclusive=true
NavGraph image
In Activity.onCreate()
if (savedInstanceState == null) {
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)!!
val navController = navHost.findNavController()
if (loginComplete) {
navController.navigate(
R.id.action_emptyFragment_to_FirstFragment
)
} else {
navController.navigate(
R.id.action_emptyFragment_to_WelcomeFragment
)
}
}
when Activity is recreated, savedInstanceState is not null and fragment is restored automatically.

Categories

Resources