Throttle Vs Debounce in the view of rapid click handling

Amit Kumar
Bobble Engineering
Published in
3 min readDec 24, 2020

Many a times, we would see the issues reported that are caused due to rapid clicks on a button. Opening multiple dialogs, launching multiple instances of an activity or performing some high intensity background task again and again are some of the impacts we face.

While there are many approaches to solve this, we would always look for the one with optimal solution. That is, we would like to get the best performance along with easy to maintain and easy to extend code.

When the clicks are handled with conventional OnClickListener, we need to struggle to handle such rapid clicks by maintaining lastClickedTime and ignoring the next clicks within some TIME_INTERVAL. Though this approach works and has been used prior to RxBinding, there are chances that code becomes unreadable and it’s easy to make mistakes on top of it while extending the functionality.

Button btn = (Button)findViewById(R.id.example_btn);
Subscription buttonSub =
RxView.clicks(btn).subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// handle the click
}
});
// unsubscribe the subscription in appropriate lifecycle method

With RxBinding, clicks are handled this way. So, it’s a simple subscription in which we can write our click handling logic. Like in any other RxJava subscription, we can use multiple operators before the actual subscription is processed. One among them, here, could be debounce.

Subscription buttonSub =
RxView.clicks(btn)
.debounce(500,TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// handle the click
}
});

Debounce waits till specified TIME_INTERVAL(500 Milliseconds in our example) is elapsed and emits the item, i.e. performs our click handling and then again waits for another TIME_INTERVAL and performs the action and so on in the case of rapid clicks. Though, it solves our problem comfortably, user needs to wait for the first TIME_INTERVAL time to see the action started. To avoid this unnecessary wastage of time and to give user a better experience, we can use throttleFirst operator.

Subscription buttonSub =
RxView.clicks(btn)
.throttleFirst(500,TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// handle the click
}
});

ThrottleFirst also waits till specified TIME_INTERVAL but performs the operation before this wait. This is really useful and noticeable in case of user facing transitions like opening a screen or a dialog.

Finally, another advantage of using RxBinding way of click handling is that we can add a common error action for each RxView.click subscription and be sure that no click on any view causes a user facing crash. This is really important in the cases where — no matter what happens our app shouldn’t crash on user’s face — kind of need exists.

Consumer<Throwable> COMMON_ERROR_ACTION = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.w("CommonErrorAction","error occurred ",throwable);
}
};

And in each click handling subscription, we can add this COMMON_ERROR_ACTION

Subscription buttonSub =
RxView.clicks(btn)
.throttleFirst(500,TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// handle the click
}
}, COMMON_ERROR_ACTION);

Hope it helps.

--

--