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:

  1. Up button in the Top App Bar is tapped

  2. 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.

 
Previous
Previous

Bottom Nav with Nav Graphs in Compose

Next
Next

Bottom Navigation in Jetpack Compose