i am now currently in signupfragment and need to go to loginfragment which is my previous fragment but its not working
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
Log.e("BackPressed","Onbackpressed")
findNavController().popBackStack(R.id.signupfragment,false)
}
The second parameter to popBackStack(R.id.signupfragment,false) is popUpToInclusive:
popUpToInclusive: boolean: Whether the given destination should also be popped.
If you use popUpToInclusive of false, you are saying "pop everything up to R.id.signupfragment, but not R.id.signupfragment.
Therefore, if you are at R.id.signupfragment, this correctly pops nothing (and returns false). You want to either 1) use true for popUpToInclusive or 2) use the version of popBackStack() that doesn't take any parameters, as this is the equivalent of popping just your current destination off the back stack:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
Log.e("BackPressed","Onbackpressed")
findNavController().popBackStack()
}
Of course, Navigation already sets up the back button to call popBackStack() when you hit the back button, so besides your extra logging, your call is entirely unnecessary.
Another way of doing it easily is by putting a test back button and coding it with.
testBackButton.setOnClickListener {
val intent = Intent(this, ActivityThatYouWantToGo::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
I dont know its a good answer but its working
findNavController().popBackStack()
findNavController().popBackStack()
findNavController().navigate(R.id.loginFragment)
Related
I'm using compose-navigation(alpha09) to handle the navigation between composables
I want to remove the Splash screen when moving to the next destination (I don't want the back pressed to get back to Splash)
Following attempts did not work as expected:
navHostController.navigate(Route.login.id) {
navHostController.graph.clear()
}
navHostController.navigate(Route.login.id)
navHostController.graph.clear()
val currentDest = navHostController.currentDestination
navHostController.navigate(Route.login.id)
if (currentDest != null) {
navHostController.graph.remove(currentDest)
}
So how can I remove the Splash screen and then move to next?
In Jetpack Compose 1.0.0-rc01 to navigate and remove previous Composable from back stack You can use:
navController.navigate(Screens.Login.name) {
popUpTo(Screens.Splash.name) {
inclusive = true
}
}
The above code will navigate from the Splash screen to Login and will pop everything up, including the Splash screen.
Navigate to a composable - docs
For v1.0.0-alpha09 (And 1.0 stable)
Using popUpTo(0) you can clear the stack before navigating to the next destination. So:
navHostController.navigate(Route.login.id) {
// popUpTo = 0 // DEPRECATED
popUpTo(0)
}
For a consistent reusable function that does not need to be aware of the current route, use this NavOptionsBuilder extension function
fun NavOptionsBuilder.popUpToTop(navController: NavController) {
popUpTo(navController.currentBackStackEntry?.destination?.route ?: return) {
inclusive = true
}
}
^ Similar to other answers, it popUpTo the current route, but rather than needing to name the specific current route, it instead gets it from the backstack entry.
Now you can use it like so:
navController.navigate(ScreenRoutes.Login.route) { popUpToTop(navController) }
^ That example navigates to Login, and should clear the entire backstack before it.
For clearing all back stack
To remove multiple composable screens from the stack use the below snippet
navController.navigate(ScreenRoutes.Login.route){
popUpTo(navController.graph.findStartDestination().id){
inclusive = true }}
Or To keep Home in back stack
navController.navigate(ScreenRoutes.SelectCourseLayout.route){
popUpTo(ScreenRoutes.Home.route)
}
Apart from screens, back stack contains navigational graphs, and its root is always the first thing in back stack. Our NavHostController contains graph, so by popping its id, you are able to clear your back stack:
popUpTo(navHostController.graph.id)
For more info, here is the detailed explanation https://medium.com/#banmarkovic/jetpack-compose-clear-back-stack-popbackstack-inclusive-explained-14ee73a29df5
To clear the back-stack, you can simply create this Extension function and reuse it wherever applicable.
fun NavHostController.navigateAndClean(route: String) {
navigate(route = route) {
popUpTo(graph.startDestinationId) { inclusive = true }
}
graph.setStartDestination(route)
}
Jetpack Compose v1.0.5
navController.backQueue.removeIf { it.destination.route == "Splash" }
navController.popBackStack()
After so many try, I've found the better way to clear the back stack during the logout scenario. Most of the production app will clear the splash or sign in screen as soon as we navigate to Home screen and there would be a multiple way to land into Home screen as well.
So, we may not know the initial screen to perform the popupTo. If there is a bottom bar, then story would be too difficult as well.
Here is a magic could that work all the scenario
val firstBackStackRoute = navController.backQueue.firstOrNull()?.destination?.route
firstBackStackRoute?.let {
navController.popBackStack(firstBackStackRoute, true)
}
I have three fragments A, B and C. And I'm using navHostFragment container in MainActivity. So the application goes from A -> B using kotlin extension function findNavController().navigate... and then go from B to C using same function. All works fine till here.
Now in Fragment C, I'm replacing different elements on fragment C using
activity?.supportFragmentManager
?.beginTransaction()
?.replace(R.id.list_container, someFragment)
?.addToBackStack("some_frag_id")
?.commit()
The list_container is replaced with someFragment. After this when I press physical back button Fragment C pops out and my app goes to Fragment B while what I expect it to restore replaced list_container i.e. whatever was there before replacement.
I'm also overiding this in my MainActivity
override fun onBackPressed() {
val count = supportFragmentManager.backStackEntryCount
if (count == 0) {
super.onBackPressed()
//additional code
}
else {
supportFragmentManager.popBackStack()
}
}
I'm not sure what is missing here. I have read a lot of solutions on stackoverflow but none worked to my satisfaction. Please guide.
If you are adding a Fragment to a View within a Fragment, you must always use the childFragmentManager - using activity?.supportFragmentManager is always the wrong FragmentManager to use in that case.
Besides fixing cases with restoring state (which would not work when using the wrong FragmentManager), this also ensures that the default behavior for dispatching onBackPressed() down the FragmentManager hierarchy will work out the box - you should not need any logic at all in onBackPressed() to have the pop work correctly.
If you need to intercept the back button in Fragment C, you should follow the providing custom back documentation to register an OnBackPressedDispatcher - you should not override onBackPressed() even in those cases.
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.
What I am exactly looking for is that wether my fragment is already in the back stack or not.
For example, I navigate to Fragment A to Fragment B. Later, I navigate to Fragment B to Fragment C. Now, I want to check in the Fragment C that is there Fragment A available in the back stack. If yes, I want to pop out all the fragments up to Fragment A else I want to add new Fragment A.
Please make sure I want to check availability of Fragment A in Fragment C.
Is there any luck?
Thanks in advance.
What I am exactly looking for is that wether my fragment is already in
the back stack or not.
There is a way to do that using NavController.
try {
val back:NavBackStackEntry = controller.getBackStackEntry(R.id.nav_a)
Log.d("in_back_stack", back.destination.label.toString())
} catch (ex: IllegalArgumentException){
Log.d("in_back_stack","no_entry")
}
Using NavController.getBackStackEntry(...) & destinationId you can easily know whether a fragment is already in the back stack or not. But be careful using this method. As the method will throw an IllegalArgumentException if the destinationId is not found in the back stack.
Please make sure I want to check availability of Fragment A in Fragment C.
You can check from anywhere. All you need is reference of the NavController.
fun NavController.isInBackStack(#IdRes id: Int): Boolean = runCatching {
getBackStackEntry(id)
}.isSuccess
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.