← All shorts
29Android 14+2 min read

Predictive Back Gestures in Compose

Animate the UI as the user drags the back gesture instead of jump-cutting away once it completes.

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 try block.
  • A CancellationException means the user released mid-swipe without completing it; the catch block 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.

androidcomposenavigation

Related shorts