3

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
        }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Hi @Thracian, I'm not sure if I have to open another thread post about this, though I think I'm having the same issue and unable to find any good source about this, do you have any update on this?.. thank you. – z.g.y Oct 01 '22 at 01:35
  • There is linked question asks the same thing. Abhimanyu opened an issue for this. It's still open. https://issuetracker.google.com/issues/241705563 – Thracian Oct 01 '22 at 02:29
  • Let's hope it gets added in the future. I saw another question asking same thing recently. – Thracian Oct 01 '22 at 02:35
  • 1
    Yep, though just to mention, for my use-case, I followed certain posts on how to detect if the `softkeyboard` is open or closed, and just defined a callback to listen to the changes, so far it serves its purpose, but having this issue resolved and covered in compose would simplify such workarounds. Thank you again @Thracian – z.g.y Oct 01 '22 at 02:38
  • 1
    Same. I used a workaround for the question above either. – Thracian Oct 01 '22 at 02:50

1 Answers1

0

Using ujizin's solution on Oct 11, 2021 for the SO post How can I detect keyboard opening and closing in jetpack compose, I managed to make it work like a charm!

Example

var prevKeyboardState by remember { mutableStateOf(KeyboardState.Closed) }
val keyboardState by keyboardAsState()

LaunchedEffect(keyboardState) {

    if (prevKeyboardState == KeyboardState.Opened) {
        /* any actions on Keyboard close */
    }

    prevKeyboardState = keyboardState

}

KeyboardState.kt

enum class KeyboardState {
    Opened, Closed
}


@Composable
fun keyboardAsState(): State<KeyboardState> {

    val keyboardState = remember { mutableStateOf(KeyboardState.Closed) }
    val view = LocalView.current

    DisposableEffect(view) {

        val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {

            val rect = Rect()

            view.getWindowVisibleDisplayFrame(rect)

            val screenHeight = view.rootView.height
            val keypadHeight = screenHeight - rect.bottom

            keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
                KeyboardState.Opened
            } else {
                KeyboardState.Closed
            }

        }

        view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)

        onDispose {
            view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
        }

    }

    return keyboardState

}
moken
  • 3,227
  • 8
  • 13
  • 23