How to pop through fragment stack in NavController without loosing "parent" state - android

I open some main fragment FragA, which then can open other fragments, that are added to the stack, but when I press back I show a DialogC, which should clear the stack and get me back to FragA, without loosing it's state, restore it from stack, rather then creating it - to recreate it I'll have to pass some arg through the whole stack.
I tried some configs with popUpTo in different places, and also used findNacController.popUpTo(with/out_aruments) or findNavController.navigate(R.id.action_dialog_c_to_frag_a) without destination defined in action, but pop can't find action in stack, navigate wants to recrete fragment when destination is defined, withou it cannot find pop action in stack (I/NavController: Ignoring popBackStack to destination frag_a as it was not found on the current back stack)
This is sample of my nav_graph:
<?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">
<fragment
android:id="#+id/asdf"
android:name="SomeNaviFragment">
<action
android:id="#+id/action_asdf_to_frag_a"
app:destination="#id/frag_A" />
</fragment>
<fragment
android:id="#+id/frag_A"
android:name="FragA">
<argument
android:name="some_id"
app:argType="integer" />
<action
android:id="#+id/action_frag_a_to_frag_b"
app:destination="#id/frag_B" />
</fragment>
<fragment
android:id="#+id/frag_B"
android:name="FragB">
<action
android:id="#+id/action_frag_b_to_dialog_frag_c"
app:destination="#id/DialogFragC"/>
</fragment>
<dialog
android:id="#+id/DialogFragC"
android:name="DialogC">
<action
android:id="#+id/action_dialog_c_to_frag_a"
app:popUpTo="#id/frag_a"
app:popUpToInclusive="true"/>
</dialog>
</navigation>
In short - I wan't to go deeper from FragA through some fragments, but when the DialogC shows up in some point, I want to get back to FragA, to it's initial state. Is it possible to achive it without passing the creation arguments for FragA?
Some solution already tried, like: Navigate Back with Navigation Component with it's linked resources, but this didn't help at all.

I've used the article in your post to make this https://github.com/yoobi/backNavigation I hope it helps.
EDIT: you're looking for a combination of this and the article then.
The popUpTo attribute of an action "pops up" the back stack to a given destination before navigating. (Destinations are removed from the back stack.)
If the popUpToInclusive attribute is false or is not set, popUpTo removes destinations up to the specified destination, but leaves the specified destination in the back stack.
If popUpToInclusive is set to true, the popUpTo attribute removes all destinations up to and including the given destination from the back stack.
If popUpToInclusive is true and popUpTo is set to the app's starting location, the action removes all app destinations from the back stack. The Back button takes the user all the way out of the app.
You can also check the count of your backstack with : parentFragmentManager.backStackEntryCount

Related

backstack does not clear while navigating from fragment to activity

Here is my code
<fragment
android:id="#+id/fragment1"
android:name="com.example.app.Fragment1"
android:label="SignatureFragment"
tools:layout="#layout/layout_fragment1">
<action
android:id="#+id/action_fragment1_to_main_activity"
app:destination="#id/main_activity"
app:enterAnim="#anim/slide_in_from_right"
app:exitAnim="#anim/no_anim"
app:launchSingleTop="true"
app:popEnterAnim="#anim/no_anim"
app:popExitAnim="#anim/slide_out_to_right"
app:popUpTo="#id/navigation_graph_id"
app:popUpToInclusive="true" />
</fragment>
<activity
android:id="#+id/main_activity"
android:name="com.example.app.MainActivity"
android:label="MainActivity"
tools:layout="#layout/activity_main" />
Now the code for navigation
findNavController().navigate(R.id.action_fragment1_to_main_activity)
When I navigate to activity and press back, the fragment is still there. I want to clear the backstack after opening the activity.
I tried to remove the animation and also tried with removing app:launchSingleTop, but no success.
Edit
Jetpack Navigation is intended to work with single activity and does not fully support activity navigation with parameters passed to actions
Thus to clear stack when navigating from one activity to another you will still need to call activity.finish()
Edit end
The thing is findNavController().navigate(R.id.action_fragment1_to_main_activity) wont work.
Try to navigate via navigate(#NonNull NavDirections directions). In your case it will look something like this
findNavController().navigate(
Fragment1Directions.actionFragment1ToMainActivity())
Hope it helps.

Is it possible to start with a "non-start" fragment using Android Navigation Architecture Component(Android Jetpack)?

so I have the following navigation graph:
Fragment A (start) --> Fragment B
So for some situations (firebase notifications), I need to start Fragment B directly, passing data from the notifications. Now, this works. However, when I press the back button, it results in a crash. Is it because the leading fragment (Fragment A) is not in the stack? If so, is there a way to properly handle this.
Basically, I need the backPressed action to launch the start Fragment (Fragment A) in a situation where Fragment B is launched directly without passing through Fragment A.
Below is a snippet of my graph:
<fragment
android:id="#+id/homeFragment"
android:name="dita.dev.myportal.ui.home.HomeFragment"
android:label="Home"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/action_homeFragment_to_messageDetailFragment"
app:destination="#id/messageDetailFragment"
app:exitAnim="#anim/fade_out_animation" />
</fragment>
<fragment
android:id="#+id/messageDetailFragment"
android:name="dita.dev.myportal.ui.messages.details.MessageDetailFragment"
android:label="Message"
tools:layout="#layout/fragment_message_detail">
<argument
android:name="title"
app:argType="string" />
<argument
android:name="message"
app:argType="string" />
</fragment>
I hope the link helps.
https://developer.android.com/guide/navigation/navigation-deep-link
Deep links allow for synthetic back-stacks.
Quoted from the website, "This means that when a user presses the Back button from a deep link destination, they navigate back up the navigation stack just as though they entered your app from its entry point."
Maybe trying to create a deep link for Fragment B will work.

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.

Android NavigationUI - Handle backpress when starting fragment is poped out

I am using NavigationUI of android architecture components with bottom navigation in my android app. I have the following situation
The app starts at Fragment A
Initial calculation is done and now I pop out Fragment A by navController.popBackStack()
The app goes to Fragment B (This serves as a 'Home' for the app)
Now there are more fragments say C and D
The user can navigate between B, C and D
Now the problem is in my navigation graph, my starting fragment was A (by defining app:startDestination="#id/fragmentA") but that is now pop out of the activity. Due to this, anytime if I backpress, the app just closes instead going back to the previous fragment. For example, let's say I navigate to Fragment C or D from fragment B and if I press back button, my app will close instead of going to fragment B. If the there way to 'reassign' the startDestination?
I checked the answer to this question which has a similar situation as above but using NavOptions is somehow not working. Is there any other way? Following is the code I used to pop fragment and add nav options
navController.popBackStack()
val navOptions: NavOptions = NavOptions.Builder()
.setPopUpTo(R.id.homeFragment, true)
.build()
navController.navigate(R.id.action_fragmentA_to_fragmentB, null, navOptions)
Use this navigation graph. Hope it will solve your problem.
<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/fragment_a">
<fragment
android:id="#+id/fragment_a"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_a_to_b"
app:destination="#id/fragment_b"
app:popUpTo="#id/fragment_a"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="#+id/fragment_b"
tools:layout="#layout/fragment_b">
<action
android:id="#+id/action_b_to_c"
app:destination="#id/fragment_c"/>
</fragment>
<fragment
android:id="#+id/fragment_c"
tools:layout="#layout/fragment_c">
</fragment>
</navigation>
Use the following line for navigation:
navController.navigate(R.id.action_a_to_b)
navController.navigate(R.id.action_b_to_c)

Using DialogFragment as one of the Fragment in Android NavController

I have a graph defined using XML and I added a DialogFragment as one of the Fragment in my NavGraph. when I call NavController.navigate with resId and bundle on that nav controller I don't see any Dialog being displayed. Is there any way I can use DialogFragment instead of standard Fragment?
<fragment
android:id="#+id/noLoginDialog"
android:name="com.ram.view.NoLoginDialog"
android:label="NoLoginDialog">
<argument
android:name="argTitle"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argBody"
android:defaultValue="null"
app:type="string"/>
<argument
android:name="argButton"
android:defaultValue="null"
app:type="string"/>
</fragment>
and my action is defined as below
<container_fragment
android:id="#+id/homeFragment"
android:name="com.ram.home.HomeFragment"
android:label="HomeFragment">
<action
android:id="#+id/action_homeFragment_to_noAuthAlertDialog"
app:destination="#id/noLoginDialog"/>
</container_fragment>
My other actions with activity and fragments works just fine.
hum, Navigation Library will never open an Fragment as Dialog, it just replace the first (home destination) to other destination in your NavHostFragment.
Google said:
A destination is any place you can navigate to in your app. While
destinations are usually Fragments representing specific screens..
Please provide more information, like your navigation code (java/kotlin).
Take a read on this official Google post to understand more about Navigation:
https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing
Yes you can call up DialogFragment from navigation, you will just have to explicitly tell the navigation that you are going to use dialog
<dialog
android:id="#+id/simCancelFinish"
android:name="mk.telekom.kiosk.ui.dialogs.SimCancelFinish"
android:label="SimCancelFinish" >
<action
android:id="#+id/action_simCancelFinish_to_stornoSimFragment"
app:destination="#id/stornoSimFragment"
app:popUpTo="#id/stornoSimFragment"
app:popUpToInclusive="true" />
</dialog>

Categories

Resources