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