Intercept back press in Jetpack Compose
January 5, 2022
Here is an example showing how to intercept a back press in Compose.
There are several scenarios where this can be useful: for instance showing a confirmation alert related to unsaved changed or simply saving work-in-progress as a result of going back to previous screen. For this example, we just show a Toast when back press is intercepted.
Here is what we are going to accomplish: going from List screen to Detail and then attempting to go back gets intercepted so we can do some processing. Note that usually it’s not a good idea to disable a back press entirely—this example is about intercepting a backpress, not disabling backpress.
The custom Composable responsible for this intercept is as follows:
@Composable fun BackPressHandler( backPressedDispatcher: OnBackPressedDispatcher? = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher, onBackPressed: () -> Unit ) { val currentOnBackPressed by rememberUpdatedState(newValue = onBackPressed) val backCallback = remember { object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { currentOnBackPressed() } } } DisposableEffect(key1 = backPressedDispatcher) { backPressedDispatcher?.addCallback(backCallback) onDispose { backCallback.remove() } } }
In a nutshell, we create an OnBackPressedCallback
and add it to the OnBackPressedDispatcher
that controls dispatching system back presses. We enable the callback whenever our Composable is recomposed, which disables other internal callbacks responsible for back press handling. The callback is added on any lifecycle owner change and removed on dispose.
Now we install the BackPressHandler
into the DetailScreen
Composable to perform the intercept and call onBack
lambda in both scenarios:
Up button in the Top App Bar is tapped
System back button is pressed (or back gesture navigation is used).
@Composable fun DetailScreen() { val context = LocalContext.current // onBack can be passed down as composable param and hoisted val onBack = { displayToast(context) } BackPressHandler(onBackPressed = onBack) Scaffold( topBar = { DetailTopAppBar(onBack) } ) { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { Text(text = stringResource(id = R.string.detail_screen)) } } }
@Composable fun DetailTopAppBar(onBack: () -> Unit) { TopAppBar( title = { Text(text = stringResource(id = R.string.detail_screen_toolbar_title)) }, navigationIcon = { IconButton(onClick = onBack) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = stringResource(id = R.string.back), ) } } ) }
We can define a custom onBack
lambda and hoist it for every screen Composable that needs it.
Full source code can be found here.