Update
With the new WindowInsets.isImeVisible Experimental API, it gets easier, but it's important to check the lifecycle to not start as an opened state.
First, to return the correct values, you need to set:
WindowCompat.setDecorFitsSystemWindows(window, false)
Then to use Keyboard as a state:
enum class Keyboard {
Opened, Closed
}
/** if you want, you can use boolean instead to return an enum. */
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun keyboardAsState(): State<Keyboard> {
val lifecycle = LocalLifecycleOwner.current.lifecycle
val isResumed = lifecycle.currentState == Lifecycle.State.RESUMED
return rememberUpdatedState(if (WindowInsets.isImeVisible && isResumed) Keyboard.Opened else Keyboard.Closed)
}
use example:
val keyboardState by keyboardAsState() // Keyboard.Opened or Keyboard.Closed
ps: WindowInsets.isImeVisible has been experimental yet.
Without an experimental API
if you want with statement, I found this solution:
enum class Keyboard {
Opened, Closed
}
@Composable
fun keyboardAsState(): State<Keyboard> {
val keyboardState = remember { mutableStateOf(Keyboard.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) {
Keyboard.Opened
} else {
Keyboard.Closed
}
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
onDispose {
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
}
}
return keyboardState
}
and to detect/check the value you'll only need this:
val isKeyboardOpen by keyboardAsState() // Keyboard.Opened or Keyboard.Closed