Reputation: 49
I'm writing a Wear OS application, and have a few places in my app, where if the user goes back, I want to redirect the user to a different screen first, then let them go back as desired.
I was using BackHandler, but noticed in testing that it seemed to only work with a back button in the emulator, but swiping back like I do on my physical test device (which has no back button) is not caught by BackHandler.
Is there a way in Wear OS / Jetpack Compose to handle a swipe back in addition to the back button or a way to override swiping from the left edge of the screen to do a different action than take the user back?
Upvotes: 2
Views: 1425
Reputation: 23
I solved this by adding userSwipeEnabled
paramater inside SwipeDismissableNavHost
and then setting it true or false using state depending on composable that it requires it. Be sure to update material to latest one, I am using 1.3.0
Upvotes: 0
Reputation: 582
This is the Composable I've ended up with after lots of trial and error:
/**
* This detects a user's swipe to dismiss gesture.
*/
@Composable
fun OnDismissed(
/** Obtain via `navController.currentBackStackEntryAsState()` */
backStack: State<NavBackStackEntry?>,
/**
* Obtain like this
* ```
* val swipeBoxState = rememberSwipeToDismissBoxState()
* val navState = rememberSwipeDismissableNavHostState(swipeBoxState)
* SwipeDismissableNavHost(
* ...
* state = swipeBoxState
* )
*/
swipeBoxState: SwipeToDismissBoxState? = null,
/**
* Called when the screen is dismissed.
*/
onDismissed: () -> Unit
) {
var targetValue by remember { mutableStateOf<SwipeToDismissValue?>(null) }
val backStackValue = backStack.value
if (backStackValue != null) {
val id = rememberSaveable { backStackValue.id }
// Here, we compare the id upon creation of this composable with the current id of the backstack.
// We only want to call onDismissed() on the current screen and not any previous screens.
val isCurrentRoute = id == backStackValue.id
if (isCurrentRoute && swipeBoxState != null) {
// This keeps the targetValue always in sync with the boxState.targetValue
LaunchedEffect(swipeBoxState.targetValue) {
targetValue = swipeBoxState.targetValue
}
DisposableEffect(Unit) {
onDispose {
// If this composable ends up being dismissed while the targetValue is "dismissed",
// we can assume that the user has swiped to dismiss the screen (instead of navigating forward, which also dismisses the screen).
// Technically, the forward navigation could happen automatically via some logic,
// but this is good enough for now until this is fixed:
// https://issuetracker.google.com/issues/292590712
if (targetValue == SwipeToDismissValue.Dismissed) {
onDismissed()
}
}
}
}
}
}
Use like this:
val navController = rememberSwipeDismissableNavController()
val swipeBoxState = rememberSwipeToDismissBoxState()
val navState = rememberSwipeDismissableNavHostState(swipeBoxState)
SwipeDismissableNavHost(
navController = navController,
startDestination = "screen1",
state = navState
) {
composable("screen1") {
OnDismissed(
backStack = navController.currentBackStackEntryAsState(),
swipeBoxState = swipeBoxState
) {
// ...
}
}
}
Upvotes: 1