Multiple navigation graph app popbackstack does not work - android

I am currently working on an app that uses android navigation components.
The app has multiple navigation graphs that cater for the different navigation in the app.
A user logs into the app, which uses bottom navigation the bottom navbar has a 5 navigation graphs.
From the settings tab, the user can then logout of the application. However, when I press the back button I am taking back to the settings screen.
The settings navigation graph looks as follows :
<?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/settings_graph"
app:startDestination="#id/settingsFragment">
<fragment
android:id="#+id/settingsFragment"
android:name="zw.myexample.ui.SettingsFragment"
tools:layout="#layout/settings_data">
<action
android:id="#+id/action_settingsFragment_to_loginActivity"
app:destination="#id/toLoginActivity"
app:popUpTo="#id/login_navigation"
app:popUpToInclusive="true"/>
</fragment>
<activity
android:id="#+id/toLoginActivity"
android:name="zw.myexample.ui.LoginActivity"
tools:layout="#layout/activity_login" />
</navigation>
In my logout function I have the following:
findNavController().navigate(R.id.action_settingsFragment_to_loginActivity)
findNavController().popBackStack()
This doesn't seem to work, I have tried multiple suggestions in the list below :
Stackoverflow suggested answers

I've figured out that you are moving from one activity (contains setting_fragment) to another activity (contains login fragments). In this scenario, you can start your login_activity after clearing top with the new task.
I've created this extension function that you can use:
fun <T : AppCompatActivity> Fragment.gotoActivityWithNoBackUp(targetActivityClass: Class<T>) {
val intent = Intent(requireActivity(), targetActivityClass)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireActivity().finish()
startActivity(intent)
}
and in your logout button click, copy this code:
gotoActivityWithNoBackUp(LoginActivity::class.java)

Related

Unexpected BottomNavigationView behavior: bottom tab bar creates new screen each time user click on menu item

I want to create user flow like this:
Login screen (w/o navigation bar) -> Home Screen (with navigation bar, includes backstacks for Album and Profile)
Application starts with an authentication screen. When the user clicks “Login” - I remove it from backstack using “to_albums_action” and navigate to the albums screen.
Everything works great except strange stack behavior. When the user goes from Albums to Profile, when clicks Albums again - android opens it as a new instance.
So, when the user goes: Albums -> Profile -> Albums, then starts pressing the back button he starts hopping back: -> Profile -> Albums -> Homescreen.
Expected behavior is: go directly to Homescreen, as Albums is the first element in the menu.
Digging around, I found that this behavior could be fixed if I won’t remove auth_nav from backstack. Maybe, because NavController calculates all includes for the menu. I also noticed this in logs:
I/NavController: Ignoring popBackStack to destination com.project:id/signin_dest as it was not found on the current back stack
There some of my files:
main_nav.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"
android:id="#+id/main_nav">
<include app:graph="#navigation/auth_nav" />
<include app:graph="#navigation/profile_nav" />
<include app:graph="#navigation/album_nav" />
<action
android:id="#+id/to_login_action"
app:destination="#id/auth_nav"
app:popUpTo="#id/auth_nav"
app:popUpToInclusive="false" />
<action
android:id="#+id/to_albums_action"
app:destination="#id/album_nav"
app:popUpTo="#id/auth_nav" />
</navigation>
auth_nav.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"
android:id="#+id/auth_nav"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#+id/signin_dest">
<fragment
android:id="#+id/signin_dest"
android:name="com.artemromanov.navigationplayground.ui.signin.SignInFragment"
android:label="SignInFragment"
tools:layout="#layout/fragment_sign_in">
</fragment>
</navigation>
This is how I setup navigation in my MainActivity.kt inside onCreate()
private fun setNavigation(startDestination: Int) {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
val navController = navHostFragment.navController
findViewById<BottomNavigationView>(R.id.bottom_navigator).setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
Log.d("Activ", "${destination.id}, ${R.id.auth_nav}")
when (destination.id) {
R.id.signin_dest -> binding.bottomNavigator.visibility = View.GONE
else -> binding.bottomNavigator.visibility = View.VISIBLE
}
}
val navGraph = navController.navInflater.inflate(R.navigation.main_nav)
navGraph.setStartDestination(startDestination)
navController.graph = navGraph
}
nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/album_nav"
android:title="Albums" />
<item
android:id="#+id/profile_nav"
android:title="Profile" />
</menu>
Am I doing something wrong?
You just want a single instance of your fragment so when you are clicking albums inside the profile fragment you can just use the previous instance of albums.
Here, These answers will help you :
https://stackoverflow.com/a/24972247/14700539
https://stackoverflow.com/a/24972251/14700539

Unable to navigate to a destination in other graph

Using Android Navigation component in our code with SAMF (Single Activity, Multiple Fragments) approach for each functionality. Say each functionality will have own navigation graph intended to navigate between them.
e.g., the graphs are graph_a, graph_b, graph_c with its own fragments and designed to navigate as below:
graph_a >> graph_b >> graph_c
and it works as expected.
Now, there are scenarios where a Fragment(frag_b) might need to navigate another one in different navigation graph like
frag_b(graph_b) >> frag_c(graph_a)
I found the optimal solution, in this case, could be navigate using URI and tried below.
val deepLink = Uri.parse("app://login/user-signup")
findNavController().navigate(deepLink)
and added the following deeplink for to be called fragment in the respective graph.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/graph_login_flow"
app:startDestination="#id/userSignInFragment">
...
<fragment
android:id="#+id/userSignUpFragment"
android:name="com.app.login.ui.UserSignUpFragment"
android:label="EmailSignUpFragment"
tools:layout="#layout/fragment_user_sign_up">
<action
android:id="#+id/action_userSiUpFrag_to_emailVeriFrag"
app:destination="#id/emailVerificationFragment" />
<deepLink
android:id="#+id/deepLink_to_userSignUpFragment"
app:uri="app://login/user-signup" />
</fragment>
<include app:graph="#navigation/graph_home_flow" />
...
</navigation>
As per Android documentation, can navigate to any destination regardless of graphs but my attempt results in the following error.
Is there any better approach?
java.lang.IllegalArgumentException: navigation destination with deepLink app://login/user-signup is unknown to this NavController
at androidx.navigation.NavController.navigate(NavController.java:916)
at androidx.navigation.NavController.navigate(NavController.java:894)
at androidx.navigation.NavController.navigate(NavController.java:880)
at com.app.home.profile.ProfileFragment.onViewCreated(ProfileFragment.kt:82)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:298)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1232)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2390)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2125)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2081)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1977)

Navigation popUpTo and PopUpToInclusive aren't clearing the backstack

I'm new to the Android Jetpack Navigation architecture. I'm trying it out on a new app. There's one activity and a few fragments, two of them are login screen and email login screen. I defined those fragments in my navigations XML. The flow of the app is as follows:
Login screen → Email Login screen
What I want is, after navigating to the email login screen, when I press back, the app exits. Meaning the back-stack for login screen is removed. I know login screens aren't supposed to work that way, but I'm still just figuring things out.
I followed the documentation from Google's Get started with the Navigation component. It said, using app:popUpTo and app:popUpToInclusive="true" is supposed to clear the backstack, yet when I press back on email login screen, it still goes back to login instead of exiting.
So, here's what I've tried.
nav_main.xml
<fragment android:id="#+id/loginFragment"
android:name="com.example.myapp.ui.main.LoginFragment"
android:label="#string/login"
tools:layout="#layout/fragment_login" >
<action
android:id="#+id/action_login_to_emailLoginFragment"
app:destination="#id/emailLoginFragment"
app:popEnterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_right"
app:popUpTo="#+id/emailLoginFragment"
app:popUpToInclusive="true"/>
</fragment>
<fragment android:id="#+id/emailLoginFragment"
android:name="com.example.myapp.ui.main.EmailLoginFragment"
android:label="EmailLoginFragment"
tools:layout="#layout/fragment_login_email" />
LoginFragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding.emailLoginButton.setOnClickListener {
findNavController().navigate(R.id.action_login_to_emailLoginFragment)
}
return binding.root
}
I gave a click event to a button. In it, I used the Navigation Controller to navigate to the email login screen by giving it the action's ID. In the <action>, there are app:popUpTo and app:popUpToInclusive="true".
After reading the documentation over and over, as well as reading plenty of StackOverflow questions, I found those properties are supposed to remove my login screen off the back-stack. But they don't. The button does navigate to the email login screen, but when I press back, it still goes back to login screen instead of exiting the app. What am I missing?
<action
android:id="#+id/action_login_to_emailLoginFragment"
app:destination="#id/emailLoginFragment"
app:popEnterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_right"
app:popUpTo="#+id/loginFragment"
app:popUpToInclusive="true"/>
Your popUpTo is going back to the email login, and then popping it because of the inclusive.
If you will change the popUpTo to your login fragment, it will be navigated back to, and popped as well because of the inclusive flag, which will result in your desired behaviour.
I write this answer for people who have not completely understood the
way popUpTo works and I hope its example helps someone because most
examples for navigation are repetitive in most sites and do not show
the whole picture.
In any <action> if we write a value for app:popUpTo, it means we want to delete some of the fragments from the back stack just after completing the action, but which fragments are going to be removed from the back stack when action is completed?
Its order is Last In First Out so:
All fragments between the last fragment and the fragment defined in popUpTo will be removed.
And if we add app:popUpToInclusive="true", then the fragment defined
in popUpTo will also be removed.
Example:
Consider fragments from A to G in a navigation graph like this:
A->B->C->D->E->F->G
We can go from A to B and then from B to C and so on. Consider the following two actions:
An action E->F we write:
<action
...
app:destination="#+id/F"
app:popUpTo="#+id/C"
app:popUpToInclusive="false"/>
And for F->G we write:
<action
...
app:destination="#+id/G"
app:popUpTo="#+id/B"
app:popUpToInclusive="true"/>
Then after going from E to F using the action E->F, the fragments between the last fragment (F) and C (which is defined in popUpTo of E->F) will be removed. The fragment C will not be removed this time because of app:popUpToInclusive="false" so our back stack becomes:
A->B->C->F (F is currently on Top)
Now if we go to fragment G using action F->G :
all fragments between the last fragment(G) and B (which is defined in popUpTo of F->G ) will be removed but this time the fragment B will also be removed because in F->G action we wrote app:popUpToInclusive="true" . so back stack becomes:
A->G (G is on top now)
These 2 lines make the trick works:
If you want to go from A to B and expect to finish A:
You need to call B with this action:
<fragment
android:id="#+id/fragmentA"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_call_B"
app:destination="#+id/fragmentB"
app:popUpTo="#id/fragmentA"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/fragmentB"
tools:layout="#layout/fragment_b">
</fragment>
If you put log to your fragments you can see that fragmentA is destroyed after calling fragmentB with this action.
You can do it in XML just like this answer does, or you can also do it programmatically:
NavOptions navOptions = new NavOptions.Builder().setPopUpTo(R.id.loginRegister, true).build();
Navigation.findNavController(mBinding.titleLogin).navigate(R.id.login_to_main, null, navOptions);
Let's say that your app has three destinations—A, B, and C—along with actions that lead from A to B, B to C, and C back to A. The corresponding navigation graph is shown in figure
With each navigation action, a destination is added to the back stack. If you were to navigate repeatedly through this flow, your back stack would then contain multiple sets of each destination (A, B, C, A, B, C, A, and so on). To avoid this repetition, you can specify app:popUpTo and app:popUpToInclusive in the action that takes you from destination C to destination A, as shown in the following example:
<fragment
android:id="#+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="#layout/fragment_c">
<action
android:id="#+id/action_c_to_a"
app:destination="#id/a"
app:popUpTo="#+id/a"
app:popUpToInclusive="true"/>
After reaching destination C, the back stack contains one instance of each destination (A, B, C). When navigating back to destination A, we also popUpTo A, which means that we remove B and C from the stack while navigating. With app:popUpToInclusive="true", we also pop that first A off of the stack, effectively clearing it. Notice here that if you don't use app:popUpToInclusive, your back stack would contain two instances of destination A
popUpTo its to define the place that you want to go when you press back. If you set popUpInclusive = true, the navigation skipe that place too ( in popUpTo ).
Sample: A -> B -> A
FragmentB.kt
Attempts to pop the controller's back stack
private fun popBackStackToA() {
if (!findNavController().popBackStack()) {
// Call finish on your Activity
requireActivity().finish()
}
}
Back Stack
I faced a similar problem and my approach was simple. In the navigation graph, you have to designate the starting screen. Mine was:
app:startDestination="#id/webview"
It's called the start destination, it is the first screen users see when opening your app, and it's the last screen users see when exiting your app.
If you do not wish your login activity to be shown as you exit the app, just remove it as the start destination and use the fragment that you wish to show last in your case, It's the Email Login screen.
<?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/nav"
app:startDestination="#id/Email Login screen">
Also, make sure you override the onBackPressed() method from the host activity code as:
override fun onBackPressed() {
finish()
super.onBackPressed()
}
Now that you have removed the login fragment as the start destination, it's now not obvious what fragment will be shown first when the app opens.
Add a method to implement that in the host activity and call it from the oncreate(). In my case,i created initContent() to handle that logic. This was the code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
if (savedInstanceState == null) {
initContent()
}
}
private fun initContent() {
if (isNetworkConnected()) {
navController.navigate(R.id.webView)
} else {
navController.navigate(R.id.noInternetFragment)
}
}
Hope this helps someone.

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.

How to disable UP in Navigation for some fragment with the new Navigation Architecture Component?

I am trying out the new Navigation Architecture Component, and I can't figure out how to do this:
I have 1 Activity (MainActivity) + 3 Fragments:
SplashFragment (Home)
MainFragment
SignUpFragment
I would like to use SplashFragment to determine if I should navigate to MainFragment or SignUpFragment, but once it reaches either of those 2, you should not be able to pop back to SplashFragment. How can I do that with the new navigation component?
I tried popBackStack before and after calling navigate(R.id.action_xxx), but neither of them work (which make sense: before it has nothing to pop; after it just closes the fragment that just got added). Does that mean the only way to do that is to override onBackPress to intercept it and make sure navigateUp does not get call in those cases?
Thanks!
First, add attributes app:popUpTo='your_nav_graph_id' and app:popUpToInclusive="true" to the action tag.
<fragment
android:id="#+id/signInFragment"
android:name="com.glee.incog2.android.fragment.SignInFragment"
android:label="fragment_sign_in"
tools:layout="#layout/fragment_sign_in" >
<action
android:id="#+id/action_signInFragment_to_usersFragment"
app:destination="#id/usersFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
Second, navigate to the destination, using the above action as parameter.
findNavController(fragment).navigate(SignInFragmentDirections.actionSignInFragmentToUserNameFragment())
NOTE: If you navigate using method navigate(#IdRes int resId), you won't get the desired result. Hence, I used method navigate(#NonNull NavDirections directions).
This worked for me in alpha05 release. Add app:popUpTo="#id/nav_graph" in the action tag(inside your nav_graph.xml file).
Here "#id/nav_graph is the id of my graph or also called as the Root.
<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/nav_graph"
app:startDestination="#id/startFragment">
.......
<action
android:id="#+id/action_startFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/nav_graph"/>
.......
You can also do this in design tab:- select "SplashFragment" and select the action you want to change and then pick "root" for "Pop To"
WARNING: clearTask has been deprecated and will be remove in future release, not sure what the solution is. Please follow this issue for now to keep up to date
Oh after 10 minutes finally found the key: use clearTask.
All I have to do is add app:clearTask="true" to that specific action, or use .navigate(R.id.actionXXXX, null, NavOptions.Builder().setClearTask(true).build()), and it's done. Just make sure you add it to all the children of SplashFragment (in this case, both MainFragment and SignUpFragment).
So if you have splash fragment and main fragment and you don't want to go back to splash fragment after the main fragment below method you can achieve this
<fragment
android:id="#+id/splashFragment"
android:name="com.example.youappname.views.SplashFragment"
android:label="fragment_splash"
tools:layout="#layout/fragment_splash">
<action
android:id="#+id/action_splashFragment_to_mainFragment"
app:destination="#id/mainFragment"
app:popUpTo="#id/splashFragment"
app:popUpToInclusive="true"/>
</fragment>
In you Kotlin Splash Fragment:
private lateinit var navController: NavController
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
}
private fun navigateToMainFrag() {
navController.navigate(R.id.action_splashFragment_to_mainFragment)
}
Now when you press back button it will close the app instead of showing the splash screen
For anyone wanted to do this purely in code:
Navigation.findNavController(v)
.navigate(R.id.action_splashFragment_to_userProfileFragment2, null,
new NavOptions.Builder().setPopUpTo(R.id.splashFragment, true).build())
The sample solution is add a onBackPressedDispatcher on Owner Activity of fragment/navigation:
https://developer.android.com/guide/navigation/navigation-custom-back#implement_custom_back_navigation

Categories

Resources