Jetpack Compose Intercept back button from soft keyboard

Issue

Is it possible to intercept back button when keyboard is open? With EditText it’s possible as in answer here, is it possible for Compose either?

I have a Search Composable that invokes a search after 300ms debounce and when i click back press i want not only close keyboard but remove focus and clear query either.

val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current

val dispatcher: OnBackPressedDispatcher =
    LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher

val backCallback = remember {
    object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            if (!state.focused) {
                isEnabled = false
                Toast.makeText(context, "Back", Toast.LENGTH_SHORT).show()
                dispatcher.onBackPressed()
            } else {
                println("HomeScreen() Search Back ")
                state.query = TextFieldValue("")
                state.focused = false
                focusManager.clearFocus()
                keyboardController?.hide()
            }
        }
    }
}

DisposableEffect(dispatcher) { // dispose/relaunch if dispatcher changes
    dispatcher.addCallback(backCallback)
    onDispose {
        backCallback.remove() // avoid leaks!
    }
}

Back search is only triggered after keyboard is closed as you can see in gif, it does another search after keyboard is closed because query is not empty.

Note, I don’t want a solution to prevent doing another query, adding a previous query check does that, i want to intercept keyboard back press so only the block inside handleOnBackPressed is triggered when system back button is pressed when keyboard is open not after keyboard is closed.

enter image description here

SearchState is

class SearchState<I, R, S>(
    initialResults: List<I>,
    suggestions: List<S>,
    searchResults: List<R>,
) {
    var query by mutableStateOf(TextFieldValue())
    var focused by mutableStateOf(false)
    var initialResults by mutableStateOf(initialResults)
    var suggestions by mutableStateOf(suggestions)
    var searchResults by mutableStateOf(searchResults)
    
    var searching by mutableStateOf(false)
    var searchInProgress = searching

    val searchDisplay: SearchDisplay
        get() = when {
            !focused && query.text.isEmpty() -> SearchDisplay.InitialResults
            focused && query.text.isEmpty() -> SearchDisplay.Suggestions
            searchInProgress -> SearchDisplay.SearchInProgress
            !searchInProgress && searchResults.isEmpty() -> SearchDisplay.NoResults
            else -> SearchDisplay.Results
        }

}

And query is processed with

LaunchedEffect(key1 = Unit) {
    snapshotFlow { state.query }
        .distinctUntilChanged()
        .filter {
            it.text.isNotEmpty()
        }
        .map {
            state.searching = false
            state.searchInProgress = true
            it
        }
        .debounce(300)
        .mapLatest {
            state.searching = true
            delay(300)
            viewModel.getTutorials(it.text)
        }
        .collect {
            state.searchInProgress = false
            state.searching = false
            state.searchResults = it
        }
}

Solution

Simple add this to your textFeild modifier

.onPreInterceptKeyBeforeSoftKeyboard {
                    if (it.key == Key.Back) {
                        // handle back
                        focusManager.clearFocus()
                    }
                    false
                }

Return true to stop propagation of this event. If you return false, the key event will be sent to this SoftKeyboardInterceptionModifierNode’s child.

Answered By – shubham chouhan

Answer Checked By – Pedro (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *