Can't remove backstack using Jetypack Compose - android

i'm having really hard time understanding what is going wrong here.
Let's assume i have an app with 2 screens , Home (A) and an adding form (B).
What i'm trying to achieve is having the screen A to be in the backstack, then launching B to do some work , and the going back to A reflecting the changes made in B.
Using compose, navigating from A to B works fine with this command
navController.navigate(Screen.AddingForm.route) {
}
But when i try to navigate back (for example pressing the back button , both phisical and a custom one) with this code :
BackHandler(enabled = true) {
navController.navigate(Screen.Home.route) {
popUpTo(Screen.AddingForm.route){
inclusive = true
}
}
}
if (viewModel.isBackPressed) {
navController.navigate(Screen.Home.route) {
popUpTo(Screen.AddingForm.route){
inclusive = true
}
}
}
What happens is that the A screen is shown, but if i press again the back button, the B is shown.
That does not makes any sense to me, is anything wrong with my code?
EDIT:
As suggested, i removed the BackHandler and modified the logic as it follows:
if (viewModel.isBackPressed) {
navController.popBackStack(route = Screen.Home.route, inclusive = false)
}
But with the physical back button and the one that triggers the code, the same result happens, the Form (B) is redrawn and the navigation remains where it started

Related

hide navigation drawer item on switch account

I searched the web but couldn't find anything about it.
I have a little problem with hiding some items in the navigation drawer.
In my android app (kotlin), I have two types of users, user A has two buttons in the navigation drawer, while user B, having restrictions, must not have these features.
The basic idea is that when the drawer is opened, some checks are made, calling up a function, to hide/show them.
The process works well, except that it takes a few minutes to switch between accounts. The main problem lies in the following flow:
user A home -> logout -> login -> user B home
The items/buttons are visible for a few seconds and there could be the possibility that they are pressed.
The problem I think is because the two fragments (home and login) are part of the same activity.
My solution was, upon logout, to hide those keys, but sadly the menu ui breaks like this:
I have tried to hide items through the following codes:
private fun innerLogout() {
ThreadUtils.runOnUiThread {
menuWriteNfc.isChecked = false
menuWriteNfc.isVisible = false
menuCode.isChecked = false
menuCode.isVisible = false
}
...
}
I also tried to use the following functions but it didn't help:
navView.invalidate()/navView.postInvalidate()
navView.menu.clear()
navView.inflateMenu(R.menu.navigation_menu)
Thanks in advance for your help

Create Instagram like bottom navigation with separate backstack

I'm new to android development and need some guidance. I'm trying to implement functionality like Instagram Bottom Navigation. Let me describe what do I mean by the functionality.
Desired Functionality
Bottom Navigation should display persistently on most of the screens (excluding a few i.e. AddPostScreen)
I have this bottom navigation with Home, Discover, AddPost, Market, and Profile. Tapping on each option should bring the relative screens. (I know it's very basic bottom navigation functionality I'm mentioning it here to better understand the next point)
Opening a new screen will open on top of the original screen. For example, If I'm at the Profile screen and open the follower's screen, It should open on top of the profile so that closing the follower's screen should bring the profile. From the Followers screen, other screens can also be opened just like this. If I open screens in this order:
Profile->followers->A->B
then closing (both from the on-screen back button and mobile's android back button) B will bring A, closing A should bring followers, and so on.
Second tap on an option of bottom navigation should bring the original screen and close all the screens on top of it. In the above example If the user taps the profile, the other screens (followers, A, B) should close. Profile screen should show with the same state.
For each option (of bottom navigation) multiple screens can be opened on top of original ones. Like this Home-> A-> B->C & Profile-> D-> E->F and user should be able to go back and forth from them. Let me explain, If I'm at Home and opened the A-screen then go to Profile and opens D-screen. Now I can go back to Home with A-screen opened. As well as I can open B-screen on top of the A-screen. Now if I press back, only the screens of Home should close and screens of Profile should not be affected.
Now that the functionality is clear, I'll share what I have already done to achieve the desired functionality.
What I have already done
I created a MainActivity with (my own custom) bottom navigation and ViewPager2. Bottom navigation will be displayed persistently for all screens.
I created the Fragments for Home, Discover, Market, and Profile. And used the FragmentStateAdapter with ViewPager2 to display the original four fragments. Only four fragments because AddPost (the center option of bottom navigation) will open a new activity.
Within each fragment, I placed a fragment container to open the new fragments on top of the original ones. If from Home screen-A is to be opened, I can achieve this by creating screen-A fragment and replacing it on fragment container of the home fragment. I can open screen-B fragment from there which will again replace on the fragment container of Home fragment. I can replace fragments and pop fragments easily while I'm dealing with the same fragment container. I can achieve the first four desired functionality using this technique. But it gets trickier when implementing the last functionality.
For the last functionality, I thought I should have a separate back stack for each fragment container but that isn't the case. I only have one back stack that manages all the fragments from all the fragment containers. I tried to create my own back stack but I couldn't understand how to do it.
I have described what I want and what I have already tried. If anyone has any idea please guide me. If Instagram is doing it so there must be a way to do it.
brother first little bit know about fragment behavior when you move fragment A to fragment B, so in this case fragment A view destroy but fragment A on the back stack so it means when you back Fragment A so view reinflate not recreate fragment right.
below code manage your back stacks
bottom_navigation.setOnNavigationItemSelectedListener(BottomNavigationView.OnNavigationItemSelectedListener { item ->
if (navController.currentDestination != null) {
if (item.itemId == R.id.homeFragment) {
if (navController.currentDestination!!.label != "homeFragment") {
navController.popBackStack(R.id.homeFragment, false)
}
} else if (item.itemId == R.id.auctionFragment) {
if (navController.currentDestination!!.label != "AuctionFragment") {
if (!navController.popBackStack(R.id.auctionFragment, false)) {
navController.navigate(R.id.auctionFragment)
}
}
} else if (item.itemId == R.id.chatsFragment) {
if (navController.currentDestination!!.label != "ChatsFragment") {
if (!navController.popBackStack(R.id.chatsFragment, false)) {
navController.navigate(R.id.chatsFragment)
}
}
} else if (item.itemId == R.id.profileFragment) {
if (navController.currentDestination!!.label != "ProfileFragment") {
if (!navController.popBackStack(R.id.profileFragment, false)) {
navController.navigate(R.id.profileFragment)
}
}
} else if (item.itemId == R.id.CategoryFragment) {
if (navController.currentDestination!!.label != "AddFragment") {
if (!navController.popBackStack(R.id.CategoryFragment, false)) {
navController.navigate(R.id.CategoryFragment)
}
}
}
}
true
})

Compose-Navigation: Remove previous composable from stack before navigating

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)
}

How can i go to previous fragment when i am using navigation

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)

Handle back action for multiple view

I have an activity which have multiple piece of UI panel(you can think them as view in android), these panels will be invisible by default.
Now when user trigger action1, PanelA will display, when trigger action2, PanelB will display(at different location of the screen).
Both PanelA and PanelB is visible at the moment, now when user hit the back menu, the PanelB should disappear, and PanelA should disappear when hit the back menu again.
At first, I use View to hold different panels, however I found it is difficult to keep the state consist, for example, the activity will be a little different between PanelA and PanelB.
Then I found the fragment, however after I tried and tested, I found that the addTobackStack() can not apply to my us-case, since the PanelA and PanelB are at different location, android can not save their state by default.
So I wonder if there is any other solution for my requirement?
You need to manually handle this scenario inside onBackPressed() method of an Activity.
For instance -
#Override
public void onBackPressed() {
if (panelB.isOpened()) {
panelB.close()
} else if (panelA.isOpened()) {
panelA.close()
} else {
super.onBackPressed();
}
}
When panelB is opened, it will close only panelB and wont do anything else. Same goes for panelA, if its opened and when both the panel are closed then it will exit the app like normal.
I highly recommend to use DialogFragments here as you can call show() and dismiss() any point of time on that and they can handle custom views pretty well.
Hope it helps.

Categories

Resources