There are myriads of blog posts that showcase how to debounce the user’s input by using either rxjava or coroutines. The question is how to implement such a functionality when the project does not have these dependencies?
First of all, what is debounce? In essence debounce is a pattern that helps in preventing the repeated execution of a block of code. It does that by adding a delay between two consecutive calls to the block and by cancelling the first call when the second is requested. For example:
When the user types in her keyboard, every key stroke results in calling a block of code that renders what she typed:
By having debounce when the user types, each call gets delayed and cancelled if a new one gets requested resulting in rendering the entire text when the user is finished typing:
We are going to add the debounce functionality to the example above. The initial code is very simple:
userInput is the
EditText that the user writes in and
userResult is the
TextView that the user’s input gets rendered.
Adding the debounce functionality
There are two ways to do this. The first uses java’s Timer and TimerTask and the second android’s Handler. Both of them help in implementing the same algorithm:
- on every key stroke we first cancel any previous call request
- then we setup some kind of timer for our delay and the code that needs to be called
- and finally, when the time passes, we make the call
Timer and TimerTask
We can use
Timer to schedule the execution of a block of code after a given delay. The provided block must be a
TimerTask which will be added to a queue and when the time comes it will be executed in a background thread. This last part is very important since we cannot set anything UI related there. That’s why we use the
Timer just for the delay part and then we use the view’s
Handler to execute the actual code to the main thread:
Recommended when there is a need to do some intensive work before returning back to the main thread but besides that it could be an overkill. That’s why it might be better to use the view’s
Handler for both the delay and the execution.
Handler class packs the same functionality as the
Timer. We can use it to add a block of code (being added as a callback in a
Message instance) in a queue (the handler’s
MessageQueue) and when the time comes the message gets removed from the queue and its callback gets executed in the thread that the handler was created in. In our case, since we are using the view’s handler we can be sure that the execution will take place in the main thread.
Handler class provides methods for both adding and removing from the queue. So what we end up with is something like this:
One note about the
counter variable. For removing a particular message we need to identify it and to do that we can use what is called a token. When posting for delay, we also provide an id in the form of a counter so that we can request its deletion later on.
Since we are in Kotlin land and to avoid having the above code duplicated with various global counters for identification we can create an extension function that will pack everything together and help us in having a reusable component and more readable code:
So our code ends up looking like this:
which is very similar to the original code but provides the debounce functionality!