Android navigation component with popupTo and popUpToInclusive onResume not working - android

I'm trying to figure out how the navigation component works. Here's my fragments, SignIn Fragment and HomeFragment. In my navigation graph I have the following
<fragment
android:id="#+id/signInFragment"
android:name="com.nitsua.chatapp.screen.authentication.SignInFragment"
android:label="SignInFragment"
tools:layout="#layout/fragment_signin">
<action
android:id="#+id/navigate_to_home"
app:destination="#id/homeFragment"
app:popUpTo="#id/signInFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="#+id/homeFragment"
android:name="com.nitsua.chatapp.screen.home.HomeFragment"
android:label="HomeFragment"
tools:layout="#layout/fragment_home"/>
If I understand is correct, when I use the action: navigate_to_home, it will clear the fragment backstack until signInFragment including it because of popUpToInclusive set to true. If it is not set to true. SignInFragment will not be removed in backstack.
I tried signInFragment -> homeFragment, then homeFragment and back button. It works fine. It doesn't go to sign in and the app goes out of foreground. Then I try to open again the app from background. I expect the app to be in HomeFragment but it is in SignInFragment. The fragment backstack should store the HomeFragment transaction, is that right? then when it resume, it should resume the HomeFragment. but why it resumed the SignInFragment? Am I missing something? Can someone explain to me and recommend a way to resume to HomeFragment instead on SignInFragment?
Thank you.

You could put the signInFragment as an Activity. Check in your MainActivity if you have to show the signInActivity. If yes then start the activity.
Or you could start your homeFragment and right after that start the signInFragment if log in is required.
Then use this in your signInFragment.
navController.popBackStack(R.id.homeFragment, false)
Will pop your backstack till it reaches the first instance of homeFragment.

I ran into problem when start using navigation component but unfortunately Navigation component not provide any solution yet to call onResume when backStack or popup to another fragment .It reCreate the backfragment again when press back button.
My solution:
My data is in repository (Either getting from local Db or remote ).
Then create a LiveData in ViewModel and change his value false to true when first time load data from Api or Database .After then when user press back button check from liveData if value is true then get data from viewmodel and not calling the api.
private val localOrRemoteData=MutableLiveData()
fun setLocalOrRemoteData(status:Boolean){
localOrRemoteData.value=status
}
fun getLocalOrRemoteData():MutableLiveData<Boolean>{
return localOrRemoteData
}
getLocalOrRemoteData().observeOnce(lifecycleOwner) {
temp = it
}

Related

Why Android Navigation Component screen not go back to previous Fragment,but a method in onViewCreated of previos Fragment being called?

I have 2 fragment call CreateRoomFragment and DisplayPhotoFragment,the navigation graph is look like this:
<navigation>
<fragment
android:id="#+id/createRoomFragment"
android:name="package.room.CreateRoomFragment"
android:label="Create a room"
tools:layout="#layout/fragment_create_room">
<action
android:id="#+id/action_createRoomFragment_to_roomFragment"
app:destination="#id/roomFragment" />
<action
android:id="#+id/action_createRoomFragment_to_displayPhotoFragment"
app:destination="#id/displayPhotoFragment" />
</fragment>
<fragment
android:id="#+id/displayPhotoFragment"
android:name="package.fragment.DisplayPhotoFragment"
android:label="fragment_display_photo"
tools:layout="#layout/fragment_display_photo" >
<argument android:name="bitmap"
app:argType="android.graphics.Bitmap"/>
</fragment>
So when I wanna to move from CreateRoomFragment to DisplayPhotoFragment,I use the do as below:
NavDirections action = CreateRoomFragmentDirections.actionCreateRoomFragmentToDisplayPhotoFragment(selectedPhoto);
Navigation.findNavController(view).navigate(action);
Doing this,I can navigate to DisplayPhotoFragment.
But when I press back button of the device and also the Back arrow from the toolbar,it cant go back to CreateRoomFragment.
I tried this,but still unable to back to previous fragment:
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(),
new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
navController.navigateUp(); //I tried this
navController.popBackStack(R.id.createRoomFragment,false); //and also this
}
});
Main Problem now:
By using the code above,the screen didnt go back to previous Fragment(CreateRoomFragment).It still stuck in DisplayPhotoFragment,but at the same time,an API method in CreateRoomFragment onViewCreated section is being called.
What causing this? and how can I solve this problem?
I had the same problem. For me the issue was that I was using a LiveData boolean to decide when to go to the next fragment. When I then navigated back/up the boolean was still true so it would automatically navigate forward again.
Android maintains a back stack that contains the destinations you've visited. The first destination of your app is placed on the stack when the user opens the app. Each call to the navigate() method puts another destination on top of the stack. Tapping Up or Back calls the NavController.navigateUp() and NavController.popBackStack() methods, respectively, to remove (or pop) the top destination off of the stack.
NavController.popBackStack() returns a boolean indicating whether it successfully popped back to another destination. The most common case when this returns false is when you manually pop the start destination of your graph.
When the method returns false, NavController.getCurrentDestination() returns null. You are responsible for either navigating to a new destination or handling the pop by calling finish() on your Activity.
When navigating using an action, you can optionally pop additional destinations off of the back stack by using popUpTo and popUpToInclusive parameter of the action.
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (true == conditionForCustomAction) {
CustomActionHere()
} else NavHostFragment.findNavController(this#MyFragment).navigateUp();
}
}
requireActivity().onBackPressedDispatcher.addCallback(
this, onBackPressedCallback
)
...
}
The best solution for handling navigation using live data is to use the SingleLiveEvent.
You can always use this class which is an extension of MutableLiveData.
https://gist.githubusercontent.com/cdmunoz/ebe5c4104dadc2a461f512ea1ca71495/raw/a17f76754f86a4c0b1a6b43f5c6e6d179535e627/SingleLiveEvent.kt
For a detail run down of this check:
https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70
Had a similar issue. We still have multiple activities with nav component.
So imagine activity A -> activity B, activity B has its own nav and fragments. When the initial fragment tries to pop the back stack there is nowhere to pop back to and the nav controller does not know to finish the activity. So one solution I found was to do
if (!findNavController().popBackStack()) activity?.finish()
If nav controller can not pop back it will finish activity.
You can use MutableSharedFlow instead on MutableLiveData if you want to observe the Event only once.
in your viewModel:
private val _events = MutableSharedFlow<Event>()
val events = _events.asSharedFlow() // read-only public view
suspend fun postEvent() {
_events.emit(event) // suspends until subscribers receive it
}
In your Activity/Fragment class:
lifecycleScope.launchWhenStarted {
viewModel.events.collect {
}
}
viewModel.postEvent()
This will prevent observing data continuously when going back to fragment.

popUpTo seems not to work in Navigation Component

so I'm using the android navigation component and I have a problem (2.2.0-rc04 version).
I have a welcomeFragment(wF). From wF I want to navigate to loginSellerFragment(lSF) which is in a different navigation graph. I also don't want to remove wF from backstack ( popUpTo, popUpToInclusive) when navigating to lSF because a user might wanna go back to it.
<fragment
android:id="#+id/welcomeFragment">
<action
android:id="#+id/action_welcomeFragment_to_nav_onboarding_seller"
app:launchSingleTop="true"
app:destination="#id/nav_onboarding_seller" />
</fragment>
After navigating to lSF the backstack looks like this : wF lSF
We're on lSF now, after login we want to go to feedFragment(fF) which again is in a separate graph, but this time we want to clear all the backstack, because if a user is logged in and presses back he wants the app to exit, not to take him back to wF or lSF, so I used popUpTo="#id/loginSellerFragment popUpToInclusive='true" in the action from lSF to fF.
<fragment
android:id="#+id/loginSellerFragment">
<action
android:id="#+id/action_login_to_seller"
app:destination="#+id/seller" . //this is the graph that has as firstDestination, feedFragment
app:launchSingleTop="true"
app:popUpTo="#id/loginSellerFragment"
app:popUpToInclusive="true" />
</fragment>
So in the backstack in this moment should be only fF because we removed everything up to lSF(lSF included)
The problem
When I'm on fF and press back, the app doesn't close, instead it takes me to wF ( wF should have been popped off the backstack already)
What I've tried
I've tried instead of popUpTo="#id/loginSellerFragment popUpToInclusive='true" to use popUpTo="#id/welcomeFragment popUpToInclusive='true" and it worked fine, but I'm pretty sure that this is not how it should be done. What am I missing here guys? Am I building the backstack wrong?
Also I've tried adding popUpTo="#id/welcomeFragment popUpToInclusive='true" after navigating from wF to lSF , but this will break my user experience, because I don't want the app to exit when I'm still in the login process.
Please note that all of this fragments are in separate graphs.
To navigate I use FragmentDirections e.g : findNavController.navigate(WelcomeFramgentDirections.actionXtoY())
It's not easy to grasp how Navigation Component manipulates backstack when you are using popUpTo option.
The solution you mentioned in your question is correct:
You indeed should use popUpTo="#id/welcomeFragment" popUpToInclusive="true" instead of popUpTo="#id/loginSellerFragment" popUpToInclusive="true".
I will try to explain why.
When you launch your application, your backstack will be empty and welcomeFragment will be displayed.
When you navigate from welcomeFragment to loginSellerFragment, you will have welcomeFragment in your backstack.
Than if you login, you will navigate from loginSellerFragment to feedFragment, and in backstack you will have loginSellerFragment and welcomeFragment.
Since you used popUpTo="#id/welcomeFragment", aplication will start to pop (remove) fragments from your backstack until it reaches welcomeFragment. The welcomeFragment will be also removed since we used popUpToInclusive="true".
Backstack should behave like FILO (First In Last Out) stack, so it will remove fragments in this manner:
First the top fragment will be removed and that is loginSellerFragment.
Next, welcomeFragment will be the top fragment. Since we need to pop up fragments until we reach welcomeFragment this is where we stop, but welcomeFragment will be also removed because of popUpToInclusive="true" and your backstack will be empty.
If you try to navigate back from welcomeFragment, you will exit app because your backstack is empty.
I hope that this helps. You could also read more about stack data structure.
For me setting XML code didn't really solve the issue, had to use an extra line of code
findNavController()
.navigate(R.id.navigationFragment,
null,
NavOptions.Builder()
.setPopUpTo(R.id.splashFragment,
true).build()
)

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 prevent previous fragment to show up after pressing back button using navigation controller?

I am trying to use the navigation controller right now. I want to move from LoginFragment to HomeFragment. In LoginFragment I use this code below to move to HomeFragment.
Navigation.findNavController(view).navigate(homeDestination)
However, when I tap the back button in the HomeFragment, it will go back to LoginFragment, I expect that when I tap the button it will close the app.
In old way, if I use activity instead of using Fragment, I usually do something like this to get that expected behaviour:
val intent = Intent(this,HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
By using those flags, I use to get the expected behavior. But I don't how to implement the same behavior using the navigation controller.
Navigation offers a popUpTo and popUpToInclusive attributes for removing fragments from the back stack as part of a navigate() operation.
This can be set either in XML:
<!-- Add to your Navigation XML, then use navigate(R.id.go_home) -->
<action
android:id="#+id/go_home"
app:destination="#+id/home_fragment"
app:popUpTo="#+id/destination_to_pop"
app:popUpToInclusive="true"/>
Or set it programmatically:
NavOptions navOptions = new NavOptions.Builder()
.setPopUpTo(R.id.destination_to_pop, true)
.build();
Navigation.findNavController(view).navigate(homeDestination, navOptions)
You can also use the id of a <navigation> element as well.
I was following the answer of Ian but I had been having bad luck since I didn't know what the popUpTo would be.
So we have to use the id Of nav Graph there.
app:popUpTo="#+id/idOfNavGraph". //id of nav graph
<action
android:id="#+id/go_home"
app:destination="#+id/home_fragment"
app:popUpTo="#+id/idOfNavGraph". //id of nav graph
app:popUpToInclusive="true"/>
I think this should do the trick.
NavController controller = Navigation.findNavController(view);
controller.popBackStack(R.id.fragmentLogin, true);
controller.navigate(homeDestination)
Try this
val c = view.findNavController()
c.popBackStack() // current fragment will be pop up from the stack
c.navigate(DestinationFragmentID)
Obviously I'm late. Nonetheless I'd like to do my bit and expand Ian's answer. In app:popUpTo="#+id/destination_to_pop" the destination_to_pop really means the destination that you do not want to keep in the back stack. In your case, loging_fragment.
This way, when you navigate back from your HomeFragment the app will close since LoginFragment is not in the back stack anymore. You can find the related official documentation here.

On back press of fragment prevent to call onViewCreated of previous fragment using navigation graph

I am using jetpack navigation to navigate between fragments.
I have two fragment UserListFragment and ProfileFragment.
On onViewCreated method of UserListFragment called API of server and click of any item user will redirect to profileFragment.
If user press back from ProfileFragment, then it should come to userListFragment which is working. But onViewCreated method calls again so API is calling again to take data from a server. I don't want to call onViewCreate of first fragment. I want same behaviour as fragmentManager.add() not fragmentManager.replace().
Here is the code which I have written for navigating:
<fragment
android:id="#+id/navigationUserListFragment"
android:name="packageName.dashboard.UserListFragment"
android:label="fragment_userlist"
tools:layout="#layout/fragment_user_list">
<action
android:id="#+id/actionToProfile"
app:destination="#id/navigationProfile"
app:launchSingleTop="true"
app:popUpTo="#id/navigationUserListFragment" />
</fragment>
<fragment
android:id="#+id/navigationProfile"
android:name="package.dashboard.ProfileFragment"
android:label="profile_fragment"
tools:layout="#layout/fragment_profile" />
So my question is to how can I prevent to call onViewCreated when user presses back of the second Fragment.
Thanks in advance
OnViewCreated method will call whenever you come again to UserListFragment from ProfileFragment because whenever you are going to open new profile fragment then previous fragment's view will be destroyed.
For your task, I think you should use LiveData mechanism or manage your List data condition based if it has value then API should not call. Yes If there are any changes from profile screen then Interface through communicate.

Categories

Resources