5

I've got some elements that have focus in a bottom sheet. I want to collapse the bottom sheet when the system back button is pressed, which is easy enough:

  BackHandler(enabled = bottomSheetState.isExpanded) {
    scope.launch {
      bottomSheetState.collapse()
    }
  }

The problem is, if an element is focused in the bottom sheet, the back handler's logic only gets triggered after pressing the back button twice: once to remove focus from the element (outlined text field) and once more to trigger the collapse.

I've tried using the LocalFocusManager.current to combine clearing focus when back is pressed, but the back logic isn't triggered until the element has already lost focus.

I can clear focus on collapse, so I guess the real problem is having to press back twice when the sheet is 1) visible and 2) has a child element focused.

Is there a way to stop focused elements from superseding my back logic?

Carter Hudson
  • 1,176
  • 1
  • 11
  • 23

1 Answers1

1

Hi @Carter I have found the issue on the official tracker find the link here. It has been fixed in compose 1.1.0-alpha03 but since the library is an alpha candidate I don't know how safe it would be for production. There is also a workaround mentioned here

a cleaner approach would be :

  1. Create this manager
/***
 * Compose issue to be fixed in alpha 1.03
 * track from here : https://issuetracker.google.com/issues/192433071?pli=1
 * current work around
 */
class KeyBoardManager(context: Context) {

    private val activity = context as Activity
    private var keyboardDismissListener: KeyboardDismissListener? = null

    private abstract class KeyboardDismissListener(
        private val rootView: View,
        private val onKeyboardDismiss: () -> Unit
    ) : ViewTreeObserver.OnGlobalLayoutListener {
        private var isKeyboardClosed: Boolean = false
        override fun onGlobalLayout() {
            val r = Rect()
            rootView.getWindowVisibleDisplayFrame(r)
            val screenHeight = rootView.rootView.height
            val keypadHeight = screenHeight - r.bottom
            if (keypadHeight > screenHeight * 0.15) {
                // 0.15 ratio is right enough to determine keypad height.
                isKeyboardClosed = false
            } else if (!isKeyboardClosed) {
                isKeyboardClosed = true
                onKeyboardDismiss.invoke()
            }
        }
    }

    fun attachKeyboardDismissListener(onKeyboardDismiss: () -> Unit) {
        val rootView = activity.findViewById<View>(android.R.id.content)
        keyboardDismissListener = object : KeyboardDismissListener(rootView, onKeyboardDismiss) {}
        keyboardDismissListener?.let {
            rootView.viewTreeObserver.addOnGlobalLayoutListener(it)
        }
    }

    fun release() {
        val rootView = activity.findViewById<View>(android.R.id.content)
        keyboardDismissListener?.let {
            rootView.viewTreeObserver.removeOnGlobalLayoutListener(it)
        }
        keyboardDismissListener = null
    }
}
  1. Create this composable
@Composable
fun AppKeyboardFocusManager() {
    val context = LocalContext.current
    val focusManager = LocalFocusManager.current
    DisposableEffect(key1 = context) {
        val keyboardManager = KeyBoardManager(context)
        keyboardManager.attachKeyboardDismissListener {
            focusManager.clearFocus()
        }
        onDispose {
            keyboardManager.release()
        }
    }
}
  1. Apply on application level
setContent {
        AppKeyboardFocusManager()
        YourAppMaterialTheme {
            ...
        }
    }
Chetan Gupta
  • 1,477
  • 1
  • 13
  • 22