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 — scale, translation, alpha — tied directly to the finger's position, matching the system's predictive-back preview. - 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 — or catch too broadly and swallow the cancellation without resetting swipeProgress — and the screen stays visually scaled or offset forever after a cancelled swipe. The system aborts the navigation. But nothing tells Compose to spring the UI back to normal unless you handle that case explicitly.