Technical blog from Craig Russell.
Capturing the done button on an Android software keyboard is hard, as each keyboard might implement this differently. This post describes an interesting bug report about a user’s third party keyboard not submitting when they hit their done button, and what I had to do to fix it.
Say you have an EditText
and when the user is done typing, you’d like them to be able to use their keyboard’s done button to submit the input.
You search around and find a post on StackOverflow that recommends setting an onEditorActionListener
on the EditText
which will allow you to capture the done or enter buttons on the keyboard. You test on your devices and it works well.
editText.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, keyEvent ->
if (actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER) {
userEnteredQuery(editText.text.toString())
return@OnEditorActionListener true
}
false
})
And then you actually speak to your users. So here’s what you see when you’re testing your app day to day.
And here’s what your users are actually doing! Third party keyboards… 🤣
The user hits the done button and instead of our app getting notified of this as an editor action, it just adds a space character to the EditText
. Well that’s not what we want!
Android’s customisability is a blessing for power users, but it is also a curse for developers.
Digging into this particular bug report, I had to install some third party keyboards and see how each handle the done button press. It isn’t pretty.
It turns out that some keyboards don’t submit an editor action for the done button, but instead submit a \n
new line character. 🙄
To catch this \n
character as it arrives, we need to make use of a TextWatcher
. We can hook into the onTextChanged
method to be notified that the text is being changed, and use this an opportunity to detect if the change is actually the user typing normal characters or if the user has pressed their done button.
If we detect the \n
character we first remove that character, as we don’t want it to be shown to the user as a space character (which is how EditText
tries to render it), and then use this as an opportunity to submit the user’s query.
override fun onTextChanged(
charSequence: CharSequence,
start: Int,
before: Int,
count: Int
) {
// some 3rd party keyboards submit \n instead of an IME action
if (before == 0 && count == 1 && charSequence[start] == '\n') {
editText.text.replace(start, start + 1, "")
userEnteredQuery(editText.text.toString())
}
}
We need to use both an onEditorActionListener
and a TextWatcher
. It is a bit hacky, but at least it gives us an opportunity to react to the user’s done button press.
editText.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, keyEvent ->
if (actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER) {
userEnteredQuery(editText.text.toString())
return@OnEditorActionListener true
}
false
})
editText.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(
charSequence: CharSequence,
start: Int,
before: Int,
count: Int
) {
// some 3rd party keyboards submit \n instead of an IME action
if (before == 0 && count == 1 && charSequence[start] == '\n') {
editText.text.replace(start, start + 1, "")
userEnteredQuery(editText.text.toString())
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(editable: Editable) {}
})
It turns out this particular behaviour is only happening with this keyboard when the EditText
has a textShortMessage
set as an input type option.
android:inputType="textUri|textShortMessage|textNoSuggestions"
Removing textShortMessage
means the done button is routed to the editor action listener as we would expect, and the special \n
handling isn’t necessary. 🤷♂️
Whether that is the case for all 3rd party keyboards remains to be seen. 😅
Home