Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debouncing button clicks using Rx

I'm trying to make a simple "button debouncer" which will count filtered clicks and display it thru a TextView. I want to filter rapid/spam clicks in a way that clicks with less than 300ms time-gap in-between are ignored.

I did my research and stumbled upon Rx's awesome debounce() which in theory should do the exact thing I wanted..

..or so I thought. As the app seemed to only register the first click; the counter won't increment no matter how long I tried to wait.

Here's a piece of my code:

    ...

    RxView.clicks(mButton)
        .debounce(300, TimeUnit.MILLISECONDS)
        .subscribe(new Subscriber<Object>() {
            public int mCount;

            @Override
            public void onCompleted() {
            }

            @Override
            public void onError(Throwable e) {
            }

            @Override
            public void onNext(Object o) {
                mText.setText(String.valueOf(++mCount));
            }
        });

    ...

What am I doing wrong? I've tried to run the thing without debounce() and it worked flawlessly (the counter will increment everytime the button got clicked).

Thanks in advance!

like image 693
Hadi Satrio Avatar asked Sep 10 '15 09:09

Hadi Satrio


1 Answers

Note the following in the documentation on the debounce operator:

This variant operates by default on the computation Scheduler (...)

Or, code-wise, this currently happens:

public final Observable<T> debounce(long timeout, TimeUnit unit) {
    return debounce(timeout, unit, Schedulers.computation());
}

As a result, the subscriber's callbacks are invoked on that same computation scheduler, since nothing is explicitly instructing otherwise.

Now, attempting to update a view (that's what's happening in onNext()) from any other thread than the main/ui thread, is a mistake and it will lead to undetermined results.

Fortunately, the remainder of the quote above provides the solution too:

(...) but you can optionally pass in a Scheduler of your choosing as a third parameter.

This would lead to:

RxView.clicks(mButton)
    .debounce(300, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
    .subscribe(...);

Alternatively, you can still let the debounce happen on the computation scheduler, but receive the notifications on the main/ui thread:

RxView.clicks(mButton)
    .debounce(300, TimeUnit.MILLISECONDS)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...);

Either way will ensure that the notifications are received on the main/ui thread and thus that the view is updated from the correct thread.

like image 163
MH. Avatar answered Nov 10 '22 02:11

MH.