I have a problem with my app that if the user clicks the button multiple times quickly, then multiple events are generated before even my dialog holding the button disappear
Put a little example here
view.safeClick { doSomething() }
@SuppressLint("CheckResult")
fun View.safeClick(invoke: () -> Unit) {
RxView
.clicks(this)
.throttleFirst(300, TimeUnit.MILLISECONDS)
.subscribe { invoke() }
}
We can do it without any library. Just create one single extension function:
fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) {
this.setOnClickListener(object : View.OnClickListener {
private var lastClickTime: Long = 0
override fun onClick(v: View) {
if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return
else action()
lastClickTime = SystemClock.elapsedRealtime()
}
})
}
View onClick using below code:
buttonShare.clickWithDebounce {
// Do anything you want
}
You can use Rxbinding3 for that purpose. Just add this dependency in build.gradle
build.gradle
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
Then in your activity or fragment, use the bellow code
your_button.clicks().throttleFirst(10000, TimeUnit.MILLISECONDS).subscribe {
// your action
}
Similar Solution using RxJava
import android.view.View;
import java.util.concurrent.TimeUnit;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.subjects.PublishSubject;
public abstract class SingleClickListener implements View.OnClickListener {
private static final long THRESHOLD_MILLIS = 600L;
private final PublishSubject<View> viewPublishSubject = PublishSubject.<View>create();
public SingleClickListener() {
viewPublishSubject.throttleFirst(THRESHOLD_MILLIS, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<View>() {
@Override
public void call(View view) {
onClicked(view);
}
});
}
@Override
public void onClick(View v) {
viewPublishSubject.onNext(v);
}
public abstract void onClicked(View v);
}
Just a quick update on GreyBeardedGeek solution. Change if clause and add Math.abs function. Set it like this:
if(previousClickTimestamp == null || (Math.abs(currentTimestamp - previousClickTimestamp.longValue()) > minimumInterval)) {
onDebouncedClick(clickedView);
}
The user can change the time on Android device and put it in past, so without this it could lead to bug.
PS: don't have enough points to comment on your solution, so I just put another answer.
Based on @GreyBeardedGeek answer,
debounceClick_last_Timestamp on ids.xml to tag
previous click timestamp.Add This block of code into BaseActivity
protected void debounceClick(View clickedView, DebouncedClick callback){
debounceClick(clickedView,1000,callback);
}
protected void debounceClick(View clickedView,long minimumInterval, DebouncedClick callback){
Long previousClickTimestamp = (Long) clickedView.getTag(R.id.debounceClick_last_Timestamp);
long currentTimestamp = SystemClock.uptimeMillis();
clickedView.setTag(R.id.debounceClick_last_Timestamp, currentTimestamp);
if(previousClickTimestamp == null
|| Math.abs(currentTimestamp - previousClickTimestamp) > minimumInterval) {
callback.onClick(clickedView);
}
}
public interface DebouncedClick{
void onClick(View view);
}
Usage:
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
debounceClick(v, 3000, new DebouncedClick() {
@Override
public void onClick(View view) {
doStuff(view); // Put your's click logic on doStuff function
}
});
}
});
Using lambda
view.setOnClickListener(v -> debounceClick(v, 3000, this::doStuff));