When the keyboard meets Coroutines
The keyboard management is one of the biggest problems of the Android platform since API 1. That was almost 12 years ago. However, there are mentions of a very promising API on the horizon. In Android 11, most probably, we will finally get the control of the Soft Input Keyboard. The SDK level 30 is right now in a beta stage (July 2020) and there is currently no evidence that these functions will work on older SDKs. You can wait for a support library, that will probably be in the alpha stage for some time, or you can check what are the other options to tame the beast.
View Tree Observer
The most popular way to check if the keyboard is visible to a user (since 2012), is checking the difference in height in your root view. This works nicely, but introduce some magic numbers.
Is 200dp hight difference is enough to know the keyboard is here, or maybe it should be 100dp or 300dp?
OnLayoutChangeListener
There is another way to check the difference in height of your view. The OnLayoutChangeListener. This interface is a part of View
class, so it can be attached to any class that inherits from it. Let's see some code.
The onLayoutChange
function has 9 different parameters, the view itself and 8 parameters describing the view ends, before and after the layout changes.
This function works very similarly to the ViewTreeObserver, however, it is simple to understand and simple to use. Also, what is important for a purpose of this article, is simple to wrap with coroutine. Check it out!
Suspend Cancellable Coroutine
Coroutines offer a way to write asynchronous non-blocking code in a sequential matter. It eliminates the need for listeners and callbacks, where a lot of nesting and decentralization might make your code unreadable and hard to understand.
The suspendableCancellableCoroutine
gives us a great opportunity to give our listeners a second life.
Explanation of concept:
suspendCoroutine
is a function that suspends the execution of the coroutine andCancellable
gives us the ability to react upon cancellationremove
the listener. After onLayoutChange will be called, we are removing the listener to do not observe the view anymore, and to do not leak the coroutinecont.resume
, we are waking up our coroutine from suspension and resume the continuationcont.invokeOnCancellation
, as mentioned above, using suspendCancellableCoroutine we have a chance to react upon cancellation. In this case, we will remove the listener to do not leak the coroutine.addOnLayoutChangeListener
, last but not least, we are registering our listener for a given view
The described function will be resumed after the view size change. We should focus on the reduction in Y direction only.
Usecase
Now, let’s think about the use case. When the keyboard opens, the available space in our app shrinks. In my opinion, that results in many use cases that UI needs to challenge. Knowing exactly when the keyboard appears, we can rearrange our view, start some animation, etc. Check the below pseudo-code for some inspiration
I am using this function to synchronize views animations together with a motion layout. With the help of coroutines, the animation looks smooth and I can easily control every step of the animation.
Lately, I was working with a keyboard opening and GIF container animation in the chat app. You can check the code below:
In my opinion, it is very legible. You can read it like a book and understand each step. There is one fragment to which additional attention should be paid, the one with a timeout:
withTimeoutOrNull(VIEW_HEIGHT_REDUCTION_TIMEOUT) {
binding.messagesRV.awaitsLayoutReductionInY()
}
The function withTimeoutOrNull
, runs a given suspending block of code inside a coroutine with a specified timeout and returns null
if this timeout was exceeded. I found it very useful when it comes to animations. Sometimes the Android system can hang a bit or freeze without any particular reason. In those cases, your function will finish execution.