I'm using Android Jetpack Navigation Component for my app. It has Two screen (Fragment) which are homeFragment and loginFragment. The homeFragment is the start destination. After logout from homeFragment to loginFragment, the navigation back button shows up, it will back to homeFragment again when click, that's not correct. I expect the back button not shown in loginFragment, and if user press system back app exit. The nav_graph.xml is setting properly as this post was told:
<?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/nav_graph"
app:startDestination="#id/homeFragment">
<fragment
android:id="#+id/loginFragment"
android:name="cn.helptool.qxscales.LoginFragment"
android:label="Login" >
<action
android:id="#+id/action_loginFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#id/nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="cn.helptool.qxscales.HomeFragment"
android:label="Home">
<action
android:id="#+id/action_homeFragment_to_loginFragment"
app:destination="#id/loginFragment"
app:popUpTo="#id/nav_graph"
app:popUpToInclusive="true" />
</fragment>
</navigation>
After read android docs
Add both of loginFragment and homeFragment as top level destinations, the problem fixed.
val appBarConfiguration = AppBarConfiguration.Builder(R.id.loginFragment, R.id.homeFragment).build()
setupActionBarWithNavController(navController, appBarConfiguration)
Related
I am using navigation architecture component in my application.
Suppose I have fragment A as start destination.
I have push fragment B and Fragment C via navigation controller.
My navigation back stack is A-> B-> C.
Then I navigate to Fragment A with below action
<action
android:id="#+id/fragment_A_type"
app:destination="#id/fragment_a"
app:popUpTo="#id/fragment_a"
app:popUpToInclusive="true" />
Now My navigation back stack should be A.
But if check
parentFragmentManager.fragments back stack is
A->A
Also the swipeBackDismiss of activity is not working.
my navigation.xml file
<?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_graph">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.wearexample1.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.wearexample1.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second">
</fragment>
<action
android:id="#+id/action_global_activity_type"
app:destination="#id/FirstFragment"
app:popUpTo="#id/FirstFragment"
app:popUpToInclusive="true" />
</navigation>
I've met a problem with the back stack while starting the app from a deep link (via notification).
The general idea is that I have 3 fragments (A, B, C). The A screen works as splash screen, so I've put a popUpTo and popUpToInclusive to the action, so I can never see back to it again. My deep link destination is fragment C. To achieve the proper back stack I've merged B and C into nested navigation. Unfortunately, when I press Back button in fragment B the app is showing fragment A. My guess this is because the action from A to B with popUpTo was never called when the app was started from the deep link itself. My question is: how to avoid going back from B to A in this particular scenario?
Thanks in advance! I attach the sample code:
<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/splashFragment">
<fragment
android:id="#+id/A"
android:name="A"
android:label="A" >
<action
android:id="#+id/action_A_to_B"
app:destination="#id/B"
app:popUpTo="#id/A"
app:popUpToInclusive="true" />
</fragment>
<navigation android:id="#+id/nested_nav"
app:startDestination="#id/B">
<fragment
android:id="#+id/B"
android:name="B"
android:label="B">
<action
android:id="#+id/action_B_to_C"
app:destination="#id/C" />
</fragment>
<fragment
android:id="#+id/C"
android:name="C"
android:label="C">
</fragment>
</navigation>
</navigation>
And my DeepLinkBuilder (nothing special, same as the docs)
NavDeepLinkBuilder(requireContext())
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.userProfileFragment)
.createPendingIntent()
.send()
You have set the navGraph that contain the splash screen as the default startDestination which lead to show it when you press back button from the nested navGraph , I think the easiest solution is to do the following :
First edit you nav file to have only one navigation like :
<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/splashFragment">
<fragment
android:id="#+id/A"
android:name="A"
android:label="A" >
<action
android:id="#+id/action_A_to_B"
app:destination="#id/B"
app:popUpTo="#id/A"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/B"
android:name="B"
android:label="B">
<action
android:id="#+id/action_B_to_C"
app:destination="#id/C" />
</fragment>
<fragment
android:id="#+id/C"
android:name="C"
android:label="C">
</fragment>
</navigation>
Secondly set a listener to navController to listen when a destination change occur , like this :
yourNavController.setOnDestinationChangedListener(this);
override onDestinationChangedListener() method and check if the fragment which about to get showed is A , then finish the app like :
#override
public void onDestinationChanged(NavController controller, NavDestination destination, Bundle arguments){
if(destination.getId()==R.id.splashFragmentId){
finish();
}
}
I am using Navigation Component to build my application. I would like to implement SingleTask launch mode in the fragment navigation how can I achieve it?
For more clarity, I will detail my problem.
I have a HomeFragement(Lets Fragment A) and from that I have defined actions to the inner screens
Fragment A > Fragment B > Fragment C > Fragment D
I have a BaseFragment which extends all the above fragments in which I have implemented back click and its action using findNavController().popBackStack()
In Fragment D when the user clicks the back button as expected, it is navigating back to Fragment C. My problem comes when I have called an action whose destination fragment is Fragment A. I am calling this action on a successful event in Fragment D, that action also works well. But when the user press the back button from Fragment A it goes to Fragment C, the next back click goes to Fragment B, then the next back click to FRagment A. I should destroy Fragment B and Fragment C on the successful event called in Fragment D and should resume Fragment A.
I know this flow can be achieved by using launch mode as SingleTask of the first screen(Fragment A) if it is an Activity instead of Fragment.
My navigation graph XML file
<?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/app_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="FragmentA"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_FragmentA_to_FragmentB"
app:destination="#id/dashboardFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/fragmentB"
android:name="FragmentB"
tools:layout="#layout/fragment_b">
<action
android:id="#+id/action_FragmentB_to_FragmentC"
app:destination="#id/dashboardFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/fragmentC"
android:name="FragmentC"
tools:layout="#layout/fragment_c">
<action
android:id="#+id/action_fragemntC_to_fragementA"
app:launchSingleTop="true"
app:popUpToInclusive="true"
app:destination="#id/deliveriesFragment" />
</fragment>
</navigation>
But when the user press the back button from Fragment A it goes to Fragment C, the next back click goes to Fragment B, then the next back click to FRagment A.
If I understand correctly, your Fragment A is the start destination, like a main page, and you want the behavior of "whenever I come back to Fragment A, the next back button I click should exit the app", right?
If so, try to add action from any Fragment to Fragment A and set:
app:popUpTo="#id/fragmentA"
app:popUpToInclusive="true" />
It should look like this:
Demo: https://youtu.be/LNyk_FEkZoA
Posting my working solution, the full navigation script which includes the answer of #Sam Chen
<?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/app_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="FragmentA"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_FragmentA_to_FragmentB"
app:destination="#id/dashboardFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/fragmentB"
android:name="FragmentB"
tools:layout="#layout/fragment_b">
<action
android:id="#+id/action_FragmentB_to_FragmentC"
app:destination="#id/dashboardFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/fragmentC"
android:name="FragmentC"
tools:layout="#layout/fragment_c">
<action
android:id="#+id/action_fragemntC_to_fragementA"
app:popUpToInclusive="true"
app:popUpTo="#id/fragmentA" />
</fragment>
</navigation>
I have an activity (main) with three fragments (first, second and third). I included the 3 fragments in my activity (activity_main.xml) by using <include layout="#layout/content_main"/>.
The content_main.xml is using FragmentContainerView with id = nav_host_fragment. And this is my nav_graph.xml:
<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/FirstFragment">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.makegroups.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.makegroups.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second">
<action
android:id="#+id/action_SecondFragment_to_FirstFragment"
app:destination="#id/FirstFragment" />
<action
android:id="#+id/action_SecondFragment_to_ThirdFragment"
app:destination="#id/ThirdFragment" />
</fragment>
<fragment
android:id="#+id/ThirdFragment"
android:name="com.example.makegroups.ThirdFragment"
android:label="#string/third_fragment_label"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_ThirdFragment_to_FirstFragment"
app:destination="#id/FirstFragment" />
</fragment>
</navigation>
I have a floatingactionbuttonin my activity (first fragmentstarts first) and when I click on it, I open the third fragment.
On the third fragment I have a button (next) to navigate to the first fragment, and when I click on it, I am back to first fragment using:
Fragment frg = new FirstFragment();
FragmentManager fm = requireActivity().getSupportFragmentManager();
Now (while I am in the first fragment), I click on the button next(another button to navigate to the second fragment), then the app crashes. I found this error:
java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{c9572fa V.E...... ........ 0,0-1440,2112} does not have a NavController set
Why I am getting this error? -I tried these suggestions here, without success.
I am using Java.
EDIT:
Read the last comment with #Zain to know why I got the error.
Bu using Navigation Architecture components, The NavController is the responsible for fragment transaction and managing the back stack instead of the Support FragmentManager.
So, instead of making tranditional framgnet transactions with FragmentManager
You can move from ThridFragment to the first one by:
Navigation.findNavController(requireView()).navigate(R.id.action_ThirdFragment_to_FirstFragment);
Where action_ThirdFragment_to_FirstFragment is the id of the action you defined in the navigation graph to move from ThridFragment to FirstFragment
UPDATE:
As discussed from comments, besides replacing FragmentManager by NavController in all actions; there is another issue:
Missing of action_FirstFragment_to_ThirdFragment action from the navigation graph.
navController = Navigation.findNavController(activity, R.id.nav_host_fragment)
when using the Navigation Component you should not handle the transactions yourself, instead you define the actions between each fragment and then access those directly like this
override fun onCreate(){
val navController = this.findNavController()
button.setOnClickListener{
navController.navigate(R.id.action_FirstFragment_to_SecondFragment, null)
}
}
and your nav_graph should be like this
<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/FirstFragment">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.makegroups.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first">
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.makegroups.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second">
<action
android:id="#+id/action_SecondFragment_to_FirstFragment"
app:destination="#id/FirstFragment" />
<action
android:id="#+id/action_SecondFragment_to_ThirdFragment"
app:destination="#id/ThirdFragment" />
</fragment>
<fragment
android:id="#+id/ThirdFragment"
android:name="com.example.makegroups.ThirdFragment"
android:label="#string/third_fragment_label"
tools:layout="#layout/fragment_third">
<action
android:id="#+id/action_ThirdFragment_to_FirstFragment"
app:destination="#id/FirstFragment" />
</fragment>
val navController = Navigation.findNavController(requireActivity(), R.id.fragment_container)
navigate.navigate(R.id.fragment)
I am trying to clear all fragments from the back stack when the home fragment is displayed.
In the navigation graph I added this action
<fragment
android:id="#+id/loginFragment"
android:name="com.test.navTest.ui.fragment.LoginFragment"
android:label="fragment_login"
tools:layout="#layout/login_fragment"
>
<action
android:id="#+id/action_loginFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#+id/homeFragment"
/>
</fragment>
and In the login fragment. I tried
findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragment())
and
findNavController().navigate(R.id.action_loginFragment_to_homeFragment)
As I understand from the documentation here https://developer.android.com/guide/navigation/navigation-navigate
that popUpTo clears all the stack until the destination. But nothing is cleared when I press back.
UPDATE:
I changed this action to popUpTo=Login. It removed only the login fragment and not the 2 fragments before it. Should I add any parameter to the action of the fragments before the login
<action
android:id="#+id/action_loginFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#+id/loginFragment"
app:popUpToInclusive="true" />```
What worked for me on clearing all fragments in the backstack is to add the graph id in the popUpTo value
<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/splashFragment">
<fragment
android:id="#+id/loginFragment"
android:name="com.test.navTest.ui.fragment.LoginFragment"
android:label="fragment_login"
tools:layout="#layout/login_fragment">
<action
android:id="#+id/action_loginFragment_to_homeFragment"
app:destination="#id/homeFragment"
app:popUpTo="#+id/nav_graph"
app:popUpToInclusive="true" />
</fragment>
When I added popUpTo = #+id/login_fragment. It removed the login fragment only.