Thanks to this great answer here: https://mcmap.net/q/36205/-how-to-check-visibility-of-software-keyboard-in-android we can now track when keyboard is shown in Android... Unfortunateñy I have found some issues with it.
It turns out that for API 29 and inferior, the OnApplyWindowInsetsListener.onApplyWindowInsets
is only called:
- when activity has the "adjustResize" flag in manifest
- only works in portrait mode.
For api 30+ everything works in both portrait and landscape and without the flag.
Due to these limitations, for api 29- I am using a different method (see code below) using the OnGlobalLayoutListener.onGlobalLayout
// in activity onCreate():
val rootView = window.decorView.rootView
if (isApi30()) {
val isKeyBoardVisible = isKeyboardShownOlderApis(rootView)
Log.d("KeyBoardVisibility", "initial keyboard state is: $isKeyBoardVisible for view: $rootView")
ViewCompat.setOnApplyWindowInsetsListener(rootView, WindowInsetListener(isKeyBoardVisible))
} else {
rootView.viewTreeObserver.addOnGlobalLayoutListener(KeyBoardListener(rootView))
}
then the actual listeners:
//TODO: beware of leaks use weak reference
private class KeyBoardListener(val rootView: View) : ViewTreeObserver.OnGlobalLayoutListener {
var isKeyBoardVisible: Boolean = false
init {
isKeyBoardVisible = isKeyboardShownOlderApis(rootView)
Log.d("KeyBoardVisibility", "initial keyboard state is: $isKeyBoardVisible for view: $rootView")
}
/**
* Does not get called in api 21-29 landscape when keyboard is opened/closed
*/
override fun onGlobalLayout() {
Log.d("KeyBoardVisibility", "onGlobalLayout called: orientation: " + rootView.resources.configuration.orientation)
val keyBoardVisible = isKeyboardShownOlderApis(rootView)
if (keyBoardVisible != isKeyBoardVisible) {
Log.i("KeyBoardVisibility", "Keyboard is now ${if (keyBoardVisible) "visible" else "hidden"}")
isKeyBoardVisible = keyBoardVisible
}
}
}
/**
* Works api 30, 31
* Works api 25 in portrait with android:windowSoftInputMode="adjustResize"
*/
private class WindowInsetListener(initialKeyboardVisibility: Boolean) : OnApplyWindowInsetsListener {
var isKeyBoardVisible = initialKeyboardVisibility
@SuppressLint("LongLogTag")
override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat?): WindowInsetsCompat? {
val keyBoardVisible = insets?.isVisible(ime()) ?: false
Log.d("KeyBoardVisibility", "onApplyWindowInsets called: $v with keyboardVisibility: $keyBoardVisible")
if (keyBoardVisible != isKeyBoardVisible) {
Log.i("KeyBoardVisibilityChanged", "Keyboard is now ${if (keyBoardVisible) "visible" else "hidden"}")
isKeyBoardVisible = keyBoardVisible
}
insets?.let {
v?.onApplyWindowInsets(insets.toWindowInsets())
}
return insets
}
}
}
private fun isKeyboardShownOlderApis(rootView: View?): Boolean {
if (rootView == null) {
return false
}
val insets = ViewCompat.getRootWindowInsets(rootView)
if (insets == null) {
Log.i("KeyBoardVisibility", "insets is null")
}
return insets?.isVisible(ime()) ?: false
}
private fun isApi30() = Build.VERSION.SDK_INT >= 30
Problem: onGlobalLayout
is not called when the keyboard is shown/hidden in landscape mode, I tried on many emulators and phones, it does work on a Tablet.
I have found this sample project: https://github.com/android/user-interface-samples/tree/master/WindowInsetsAnimation but in API 21-29 the methods also don't get called, which is fine for the animations but not in my case, so there must be a problem there. I searched everywhere and did not find any information about this at all.
More info:
This will be an SDK code so I cannot expect users to set any flags in manifest or any fitsSystemWindows flags
I am using androidx.core:core-ktx:1.5.0
but 1.6.0 has the same issue.
Any ideas of what I could try? I suspect the window is different since the keyboard occupies the whole screen together with the editText but why it works with new APIS?