What Is Predictive Back Gesture?
Predictive Back Gesture refers to a preview that the device shows the user when they perform a back swipe gesture. As the user swipes on the screen, a preview of where this gesture will take them is shown. This animated preview allows the user to see their next destination before committing to completing the gesture.
You can see predictive back gesture in action starting from Android 13 once you’ve enabled it in the system’s developer settings. Beginning with Android 15, this feature is no longer hidden in the developer settings but enabled by default. And with Android 15 becoming available to the public, you may want to consider adding support for this feature to your app. Luckily, you just need to add android:enableOnBackInvokedCallback=“true”
to your application
tag in your manifest file to opt in for the predictive back gesture animations.
Starting with Android 14, you can also opt in for predictive back gesture animations at the activity
level in your manifest. This will override the flag you set at the application level for this activity.
If your app uses the lower level API, OnBackInvokedCallback
, then setting the android:enableOnBackInvokedCallback
flag to false will cause the system to ignore calls to any implementation to OnBackInvokedCallback
. It will, however, still make calls to OnBackPressedCallback
, because it is backward compatible and calls the deprecated onBackPressed
API.
Here is a quick look at what it looks like when the predictive back gesture is not enabled and when it is.
Intercepting System Back
In Jetpack Compose, we have the BackHandler
effect composable to handle back navigation. It uses the OnBackPressedDispatcher
internally and is the simplest way to intercept the system back button in Compose. You could use this to show a dialog warning the user that they will lose some data if they leave the current screen now
Be sure to update the enabled property of BackHandler
to tell the system when you no longer intend to intercept the system’s back navigation.
This solution, however, is not enough to achieve the animation effect based on the user’s back gesture. The best we can achieve is to collapse it once the gesture is complete. What we need is a value that represents the user’s dragging gesture so that we can adjust the height of the Text
accordingly. We’ll need to go one API level lower than the BackHandler
to do that.
Add Animation Based On User’s Back Gesture
First, let’s make sure to opt-in for the predictive gesture animations in our manifest file by adding android:enableOnBackInvokedCallback=“true”
to our application
tag.
Here is our Text
composable, which we’ll update to support animating its height based on the back gesture being performed.
val shortText = stringResource(R.string.tap_to_expand)
val longText = stringResource(R.string.long_text)
Text(
text = if (isTextExpanded) longText else shortText,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.clickable { isTextExpanded = !isTextExpanded }
.animateContentSize()
.padding(32.dp)
)
Next, we need to keep track of some state values.
isTextExpanded
– flag to indicate if ourText
composable is expanded or collapsedbackProgress
– aFloat
value representing the back gesture the user is currently performing. When this value isnull
, it indicates that the user is not performing a back gesture.textHeightExpanded
– the height of ourText
composable when it is expanded.textHeightCollapsed
– the height of ourText
composable when it is collapsed.
var isTextExpanded by remember {
mutableStateOf(false)
}
var backProgress: Float? by remember {
mutableStateOf(null)
}
var textHeightExpanded by remember {
mutableStateOf(0.dp)
}
var textHeightCollapsed by remember {
mutableStateOf(0.dp)
}
With that in place, we need to be notified and react to events related to the back gesture. To do this, we create our implementation of the OnBackPressedCallback
. It has a couple of handy functions that we can make use of:
handleOnBackProgressed()
– we will use theBackEventCompat
to get aprogress
value that tells us how far along the gesture is.handleOnBackPressed()
– called when the user completes the back gesture. Here, we set the state not to be expanded and clear thebackProgress
to indicate that the user is no longer performing a back gesture.
Until now, the Text
composable has not been collapsed because the user has not committed to finishing the back navigation with their gesture; so far we’ve just been modifying the height of the Text
.
handleOnBackCanclled()
– called when the user cancels the back gesture by swiping back to the edge of their screen. Here, we just reset thebackProgress
state to indicate the gesture is no longer being performed.
val onBackCallback = remember {
// We’re setting enabled to true here, because
// we’ll completely remove this callback when
// we no longer intend to intercept the system back navigation
object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
backProgress = backEvent.progress
}
override fun handleOnBackPressed() {
isTextExpanded = false
backProgress = null
}
override fun handleOnBackCancelled() {
backProgress = null
}
}
}
The remember
call is used to retain the OnBackPressedCallback
instance across recompositions.
Our callback is now ready. Next, we need to register it so that the system knows that we intend to intercept the system back navigation. To do this, we need to do the following:
- Get a reference to
OnBackPressedDispatcher
. Remember that this is the backward compatible API which uses both the oldOnBackPressed
and the newOnBackInvokedCallback
. We have access toLocalOnBackPressedDispatcherOwner
, which we can use to get an instance ofOnBackPressedDispatcher
. - Set up a
DisposableEffect
to manage whether we want to intercept the system’s back navigation. We’ll use both our instance ofOnBackPressedDispatcher
and ourisTextExpanded
state as keys, removing the callback when theText
is no longer expanded so that when the user performs a back gesture, they go to the previous screen.
DisposableEffect
executes side effects when a composable enters or leaves the composition. With its onDispose
function, it allows the cleanup of resources when – removing the callback in our case.
val backPressedDispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
DisposableEffect(backPressedDispatcher, isTextExpanded) {
if (isTextExpanded) {
backPressedDispatcher.addCallback(onBackCallback)
}
onDispose {
onBackCallback.remove()
}
}
The last thing we need to do is update our Text
composable to adjust its height as the user performs the back gesture.
- Set the height values of
Text
when it is expanded and collapsed. We do this with theonGloballyPositioned
modifier to get the height of the composable after it has been drawn.
Remember to convert the values to Dp
units. The value you receive is the height in pixels, which is different depending on the screen density of the device the app is running on.
- Conditionally, add a modifier depending on whether the user is busy performing the back gesture. If they are, then we set the minimum height of
Text
to the collapsed height we saved previously. We then set the height of the composable to a percentage of the expanded height based on how far along the back gesture has been performed.
modifier = Modifier
.onGloballyPositioned {
when {
isTextExpanded && backProgress == null -> {
// density is a `val` assigned to LocalDensity.current
textHeightExpanded = with(density) {
it.size.height.toDp()
}
}
!isTextExpanded -> {
textHeightCollapsed = with(density) {
it.size.height.toDp()
}
}
}
}
.then(
if (backProgress != null) {
Modifier
.heightIn(min = textHeightCollapsed)
.height(
textHeightExpanded * (1f - (backProgress ?: 0f))
)
} else Modifier
)
Conclusion
Predictive back gesture is a feature that shows the user a preview of their destination should they choose to commit to it. This preview can help prevent accidental navigation. Apps need to opt-in to this feature, and you can further add support by adding BackHandler
to intercept and handle the user’s attempt to navigate back. We’ve even seen how you can perform animations based on the progress of the gesture that can be used to create beautiful animations.
Master Building Large-Scale Native Android Apps
Take this 23h online course to learn everything it takes to become an industry-ready native Android developer. Learn about multi-module architecture, Gradle, Jetpack Compose, authentication, offline-first development and more.