Reliably showing the Android keyboard for ViewGroups too

The other day, I was browsing through the Android blog world (make sure to check out https://blog.danlew.net/), and found this really awesome blog post from the developers over at Square about reliably displaying the Android keyboard, which is controlled by a rather difficult API.

After trying out their code, however, I realized that it did not work for my purpose which was to focus a SearchView after clicking a button. The bug was really strange, because while we had called requestFocus() on the SearchView, when we queried it for isFocused, it would return false! Turns out, after a lot of struggling, I found that requestFocus() actually may give focus to one of the descendants of the View instead. SearchView is actually a ViewGroup, so focus was actually given to a TextView that was a child of the SearchView.

After learning about this, the fix was easy: Instead of only directly checking the SearchView for isFocused, do a recursive check on the focusedChild of the SearchView. focusedChild will return the child View that either has focus or contains a descendant View with focus, or null if no descendants have focus. The code worked something like this:

fun View.getFocusedDescendant() =
   when {
     isFocused -> this
     this is ViewGroup -> focusedChild.getFocusedDescendant()
     else -> null
   }

Including this fix, the new code for reliably showing the Android Keyboard becomes this:

fun View.focusAndShowKeyboard() {
   /**
    * This is to be called when the window already has focus.
    */
   fun View.showTheKeyboardNow() {
      /**
       * This finds the descedant of the View that has focus.
       */
      fun View.getFocusedDescendant() =
         when {
           isFocused -> this
           this is ViewGroup -> focusedChild.getFocusedDescendant()
           else -> null
         }
         
       val focusedDescendant = getFocusedDescendant()
       if (focusedDescendant != null) {
           post {
               // We still post the call, just in case we are being notified of the windows focus
               // but InputMethodManager didn't get properly setup yet.
               val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
               imm.showSoftInput(focusedDescendant, InputMethodManager.SHOW_IMPLICIT)
           }
       }
   }

   requestFocus()
   if (hasWindowFocus()) {
       // No need to wait for the window to get focus.
       showTheKeyboardNow()
   } else {
       // We need to wait until the window gets focus.
       viewTreeObserver.addOnWindowFocusChangeListener(
           object : ViewTreeObserver.OnWindowFocusChangeListener {
               override fun onWindowFocusChanged(hasFocus: Boolean) {
                   // This notification will arrive just before the InputMethodManager gets set up.
                   if (hasFocus) {
                       this@focusAndShowKeyboard.showTheKeyboardNow()
                       // It’s very important to remove this listener once we are done.
                       viewTreeObserver.removeOnWindowFocusChangeListener(this)
                   }
               }
           })
   }
}

After this fix, the keyboard showed up finally as expected!

Another unrelated, but exciting note about newer versions of Android, is that there is finally a way to check if the soft keyboard is showing without needing to do screen measurements! As of Android R, you can use the WindowInsets API to check like so:

Comments

Popular posts from this blog

First-Principles Derivation of A Bank

A Play-by-play of the Mirai botnet source code - Part 1 (scanner.c)

You can control individual packets using WinDivert!