I am using Accompanist Bottom Sheet Destinations in my project. The setup is exactly the same as shown in docs.
#Composable
fun MyApp() {
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, Destinations.Home) {
composable(route = "home") {
HomeScreen(
openBottomSheet = { navController.navigate("sheet") }
)
}
bottomSheet(route = "sheet") {
MyBottonSheet(
navigateToSomeRoute = { navController.navigate("some_route") }
)
}
composable(route = "some_route") {
SomeComposable(
onBackPress = { navController.navigateUp() }
)
}
}
}
}
Here I have a button in MyBottomSheet which opens the SomeComposable screen. But when I navigateUp from there, I reach HomeScreen i.e. the bottom sheet was popped off the backstack when I navigated away from it. How can I keep my bottom sheet in the backstack so that when I press Back in SomeComposable I go back to the previous state with MyBottonSheet open (on top of HomeScreen)?
This is not possible. As per the Navigation and the back stack documentation:
Dialog destinations implement the FloatingWindow interface, indicating that they overlay other destinations on the back stack. As such, one or more FloatingWindow destinations can be present only on the top of the navigation back stack. Navigating to a destination that does not implement FloatingWindow automatically pops all FloatingWindow destinations off of the top of the stack. This ensures that the current destination is always fully visible above other destinations on the back stack.
Bottom sheet destinations are also FloatingWindow destinations that float above the other destinations. As such, it is expected that they are automatically popped off the back stack when you navigate to anything other than another dialog or bottomSheet destination.
Related
I'm using the AnimatedNavHost component in my Jetpack Compose app to manage navigation between different composable routes. I have added multiple navigation blocks to the AnimatedNavHost, and I want to obtain the route (it came from) of a specific navigation block.
For example, I have the following NavHost set up in my app:
#OptIn(ExperimentalAnimationApi::class)
#Preview
#Composable
fun NavGraph() {
val navControl = rememberAnimatedNavController()
AnimatedNavHost(
navController = navControl,
startDestination = "home",
) {
navigation(
route = "home",
startDestination = "site1home",
) {
composable(
route = "site1home",
) {
}
composable(
route = "site2home",
) {
}
}
}
}
How can I obtain the route of the HomeRoute? I want to be able to access the route of each individual navigation block.
Example
My current Destination is "site1home" (API call for this navController.currentDestination?.route) is there a way to obtain from which nested graph main route I came from (in this example the main route is "home") so I can display it like this.
I have tried to read the NavController Documentation without success.
TLDR: Is there a GetNestedGraphMainRouteOfCurrentDestination function :D
You can use Navcontroller's backStackEntry to get the main route of the navigation.
currentBackStackEntryAsState() gets the current navigation back stack entry as a MutableState. When the given navController changes the back stack due to a NavController.navigate or NavController.popBackStack this will trigger a recompose and return the top entry on the back stack.
Below is the sample block for the same.
#Composable
fun Site1(navController: NavHostController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val mainRoute = navBackStackEntry?.destination?.parent?.route
Text(text = mainRoute)
}
The above code will show text as home
I'm using jetpack compose in my project. My app contains multiple screens and nested navigation.
So I have this in my MainActivity:
val startDestination = if (setting.isUserLoggedIn)
HomeNavGraph.HomeRoute.route
else
AuthenticationNavGraph.AuthenticationRoute.route
setContent {
val navController = rememberNavController()
DoneTheme {
NavHost(
navController = navController,
startDestination = startDestination
) {
authentication(navController = navController)
home()
}
}
}
This code decides that, based on whether the user has already logged in, navigate the user to the authentication screen or home screen. The home navigation is like this:
fun NavGraphBuilder.home() {
navigation(
startDestination = HomeNavGraph.HomeScreen.route,
route = HomeNavGraph.HomeRoute.route
) {
composable(route = HomeNavGraph.HomeScreen.route)
{
HomeRouteScreen()
}
}
}
The HomeRouteScreen() per se, has bottom navigation and its navHost.
Now I want to navigate the user from the profile screen in HomeRouteScreen() to the Authentication screen that its composable is defined in the MainActivity nav graph.
my problem is:
If I call navcontroller.navigate(AuthenticationNavGraph.AuthenticationScreen.route), I get the error that the destination is not explicitly in the current nav graph. On the other hand, if I use the navController which exists in Main Activity, in HomeRouteScreen I get the error that ViewModelStore should be set before setGraph call. So How should I handle this issue?
I have 3 tabs A, B, C in BottomNavigationView and each has a nav graph.
I can do what I want perfectly in navigation 2.3.3 by a complicated navigation extension, just like the old architecture-components-samples. This sample is now upgrade to 2.4.0. which use less code.
What I want is:
Step: Graph A' s start destination fragment A1 navigate to A2.
Step: Tap tab B or C.
B or C' s navigateUp action is back to A. (works fine)
When back to tab A, it shows A2. (in 2.4.0' s sample, shows A1)
The BottomNavigationView' s ItemReselected action is popBackStack to current graph' s startDestination. (how to set setOnItemReselectedListener if the navController does not change?)
The three startDestination fragment A1, B1, C1 are top level destinations, so B1 and C1' s toolbar do not show back icon. (works fine, because the sample set a set of these 3 fragments instead of navController.graph to AppBarConfiguration)
2.4.0 said it support Multiple back stacks. What does it mean? Can I make my BottomNavigationView in 2.4.0?
Here is how I do "3. The BottomNavigationView' s ItemReselected action..." in 2.3.3:
private fun BottomNavigationView.setupItemReselected(
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager
) {
setOnNavigationItemReselectedListener { item ->
... // get the item' s navController
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
What I do in 2.4.0:
just copy the sample code.
Add setOnItemReselectedListener and popBackStack
import android.util.SparseIntArray
import androidx.core.util.getOrElse
private val startDestinationIdByNavId: SparseIntArray by lazy(NONE) {
SparseIntArray(5).apply {
put(R.id.tab_home_nav, R.id.tabHomeFragment)
put(R.id.tab_profile_nav, R.id.tabMyProfileFragment)
}
}
fun setupViews() {
binding.bottomNavView.run {
setupWithNavController(navController)
// Pop the back stack to the start destination of the current navController graph
setOnItemReselectedListener {
navController.popBackStack(
destinationId = startDestinationIdByNavId.getOrElse(it.itemId) {
error("Unknown menu item $it")
},
inclusive = false,
)
}
}
}
I am creating a single activity app with Jetpack compose using it's navigation component. I have created composables to represent different screens, and used the navigation component as:
#InternalCoroutinesApi
#ExperimentalAnimationApi
#ExperimentalPagerApi
#RequiresApi(Build.VERSION_CODES.M)
#ExperimentalComposeUiApi
#Composable
fun ProgressNavigation(themeViewModel: ThemeViewModel?) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Constants.SPLASH
) {composable(Constants.SPLASH) {
ScreenAnimation {
Splash(navController = navController)
}
}
composable(Constants.HIW) {
ScreenAnimation {
LogoPage(false, true, navController = navController) { HowItWorks( navController = navController) }
}
}
.
.
.
}
Let's say I have 2 screens (composables) "SignUp" and "TnC".
User enters the sign up details in SignUp screen then goes to TnC screen. On coming back to
SignUp screen by pressing a button in TnC screen the details entered earlier in SignUp should remain intact. How do I implement this? If the screens were activities I would do it by popping off the screen from backstack using onBackPressed.
How do I do this in my case?
Replace by remember with by rememberSaveable in SingUp composable, then you can use navController.popBackStack() if you are just navigating back or you can use popUpTo. if you want to navigate back further.
example:
navController.popBackStack()
//or
navController.navigate("TnC") {
popUpTo("SignUp")
}
I have an Activity with a navGraph that conditionally chooses a startDestination when the the Activity is created.
private suspend fun checkStatusAndNavigate(userID: Int) {
val navController = findNavController(nav_host_container)
val user = getUserFromDB(userID)
val dateNow = Date((if (prefSaved) prefTime() else System.currentTimeMillis())
val navGraph = navController.navInflater.inflate(nav_graph).apply {
// NavGraph inflates with one of three possible starting points: A, B, or C.
startDestination = when {
checkDate(dateNow) == DateCheck.OldDate -> R.id.fragment_a
!user.hasOptedIn -> R.id.fragment_b
else -> R.id.fragment_c
}
}
navController.setGraph(navGraph)
}
After navController.setGraph(navGraph), the startDestination is launched. This works as expected.
Fragment B's only destination out is to Fragment C. But when the user is in Fragment C and does onBackPressed(), I would like to skip Fragment B, and return to whatever called the Activity (there are a few options, so overriding onBackPressed isn't a great option).
I don't think this is a popTo or popToInclusive problem. I don't want to clear the backstack. I just want to keep Fragment B from ever entering the backstack if at all possible.
If you don't want B to be accessible on back from C, then you're supposed to create an Action that is popTo=B, popToInclusive=true, and destination=C.