BackHandler fires once, after the back gesture is already committed. Too late to animate anything.
There's no way to show the in-progress swipe preview the rest of the system uses, so a screen using only BackHandler jump-cuts away while everything around it animates smoothly with the finger.
#The old way
@Composable
fun DetailScreen(onBack: () -> Unit) {
BackHandler(enabled = true) {
onBack()
}
Text("Detail")
}
#The new way
@Composable
fun DetailScreen(onBack: () -> Unit) {
var swipeProgress by remember { mutableFloatStateOf(0f) }
PredictiveBackHandler(enabled = true) { progress ->
try {
progress.collect { backEvent ->
swipeProgress = backEvent.progress
}
onBack()
} catch (e: CancellationException) {
swipeProgress = 0f
}
}
Box(
modifier = Modifier.graphicsLayer {
scaleX = 1f - swipeProgress * 0.1f
scaleY = 1f - swipeProgress * 0.1f
}
) {
Text("Detail")
}
}
#Why it matters
- The lambda receives a
Flow<BackEventCompat>. Collect it and you can drive a real-time animation from the finger's position, matching the system's predictive-back preview. Scale, translation and alpha all work. - When the flow completes normally, the gesture finished. That's the point to actually navigate back, inside the same
tryblock. - A
CancellationExceptionmeans the user released mid-swipe without completing it; thecatchblock is where any transform state gets reset.
#Gotcha
Skip the try/catch and the screen stays visually scaled or offset forever after a cancelled swipe. Same if you catch too broadly and swallow the cancellation without resetting swipeProgress. The system aborts the navigation, but nothing tells Compose to spring the UI back to normal unless you handle that case explicitly.