Manually pre-populate navigation backstack in Android/Jetpack Compose
Asked Answered
C

2

13

Is it possible to pre-populate the navigation backstack in Android/Jetpack Compose?

I have a deeplink, that navigates deep into the navigation hierarchy, however, on back press, it navigates to the root route.

Example:

Route.Main -> Route.List -> Route.Details(argument: id)

Deeplink: https://mywebsite.com/details/id

Current Behaviour: It opens Route.Details with the correct argument, however, onBack, it opens Route.Main

Desired Behaviour: It should open Route.List

I know I can manually "program" this behavior, but I would prefer to "configure" it.

Collation answered 1/12, 2021 at 17:14 Comment(1)
This is one of the reasons I ditched using Compose's navigation API and developed my own, which supports deeplinks, passing objects to screens and takes care of the issue you raised. You can try it out. The demo includes deeplinking that properly manages returning to previous screens: github.com/JohannBlake/JetmagicVaricocele
J
2

I have a similar problem before and seems like this is the library's expected behavior. I mean, if the user open the app from a deep link which displays the Screen_C and then press back, the app's home screen should be displayed. So maybe you need to revalidate the usability of the app.

However, if even though you want to make a workaround handling the back pressing manually. Using the function below

fun navigatingBack(
    navController: NavHostController,
    destinationRoute: String
) {
    val hasBackstackTheDestinationRoute = navController.backQueue.find {
        it.destination.route == destinationRoute
    } != null
    // if the destination is already in the backstack, simply go back
    if (hasBackstackTheDestinationRoute) {
        navController.popBackStack()
    } else {
        // otherwise, navigate to a new destination popping the current destination
        navController.navigate(destinationRoute) {
            navController.currentBackStackEntry?.destination?.route?.let {
                popUpTo(it) {
                    inclusive = true
                }
            }
        }
    }
}

Let's say you declare the screens ScreenA, ScreenB and ScreenC.

composable("__A__") {
    ScreenA(navController)
}
composable("__B__") {
    ScreenB(navController)
}
composable("__C__") {
    ScreenC(navController)
}

You can do the following:

@Composable
fun ScreenA(navController: NavHostController) {
    Button(onClick = {
        navController.navigate("__B__")
    }) {
        Text(text = "Screen A - Go to B")
    }
}

@Composable
fun ScreenB(navController: NavHostController) {
    Button(onClick = {
        navController.navigate("__C__")
    }) {
        Text(text = "Screen B - Go to C")
    }
    BackHandler {
        navigatingBack(navController, "__A__")
    }
}

@Composable
fun ScreenC(navController: NavHostController) {
    Text(text = "Screen C")
    BackHandler {
        navigatingBack(navController, "__B__")
    }
}

Using the example above, if you navigate directly to the ScreenC and press back, the ScreenB will be displayed. The similar happens in ScreenB, where the ScreenA is shown when the back button is pressed.

Jarl answered 17/3, 2022 at 13:44 Comment(0)
C
0

To update @nglauber's answer with the newer versions of navigation-compose where you no longer have access to navController.backQueue I would propose extending NavHostController like so.

/**
 * Attempt to pop up to the destination. If the destination is present
 * in the back stack then it will pop up to it. If the destination
 * is not present in the back stack it will navigate to it and remove
 * the current screen from the stack.
 */
fun NavHostController.popUpTo(route: String) {
  try {
    this.getBackStackEntry(route)
    popBackStack(route, false)
  } catch (exception: IllegalArgumentException) {
    navigate(route) {
      currentBackStackEntry?.destination?.route?.let {
        popUpTo(it) {
          inclusive = true
        }
      }
    }
  }
}

Then, when you want to use this it would be simplified to

BackHandler {
  navController.popUpTo("__A__")
}
Cremator answered 3/11, 2023 at 19:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.