202

What is the best way to prevent double clicks on a button in Android?

naXa
  • 26,677
  • 15
  • 154
  • 213
Androider
  • 20,243
  • 32
  • 95
  • 154

52 Answers52

402

saving a last click time when clicking will prevent this problem.

i.e.

private long mLastClickTime = 0;

...

// inside onCreate or so:

findViewById(R.id.button).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // mis-clicking prevention, using threshold of 1000 ms
        if (SystemClock.elapsedRealtime() - mLastClickTime < 1000){
            return;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        // do your magic here
    }
}
qezt
  • 4,047
  • 2
  • 10
  • 5
  • 23
    This is the only solution that actually prevents a double click. I just spent an hour trying all of the others since this method is pretty clunky, but none of them could prevent a quick double tap of a view except this one. Google please fix this :) – ashishduh Jan 27 '14 at 23:26
  • 6
    This solution works fine.This solution also works when you have multiple buttons on the screen and you want to click only single button at time. – void Mar 06 '14 at 07:41
  • 13
    This does not completely prevent double clicks if you do double click fast enough. – Loc Sep 18 '14 at 22:34
  • 4
    Best answer, it should be on top – Gennadiy Ryabkin Nov 04 '14 at 18:43
  • 3
    This solution should be the right answer... setEnabled(false) is not enough. – heloisasim Oct 02 '15 at 19:08
  • 1
    Why not hide away mLastClickTime inside the listener? new OnClickListener() { private long mLastClickTime = SystemClock.elapsedRealTime(); @Override public void onClick ... – Toerndev Dec 11 '15 at 17:16
  • 1
    It is working till now ... tested for more than 100 times. Other solutions tested for less and they failed. Logically, it should work like charm and it does. – Amt87 Nov 20 '16 at 13:37
  • This solution also works with recyclerView, ListView and so on. – oskarko Jan 23 '17 at 12:27
  • Need to say, that's the only way that I saw can use an elegant way to solve double-click or quickly click. Thanks for sharing. – Chauyan Mar 07 '17 at 04:16
  • SystemClock.elapsedRealtime() can be replaced with the pure java code like System.currentTimeMillis(), just if somebody wants to put this util method into java module not android module – Stoycho Andreev Sep 07 '17 at 20:35
  • @Sniper No. What if someone changes the clock? Then System.currentTimeMillis() will cause a big problem. – Redwolf Nov 17 '17 at 15:44
  • @Redwolf :). I can't see any problem if somebody change the clock! We are talking about something different here. We are trying to prevent double click on button. When you click for first time, get (store in variable) the current time and after certain time (maybe one sec) just reset that variable and next time when you click you will have fresh new timestamp, does not matter what the value is (as long is bigger then 0). I do not see any issue here – Stoycho Andreev Nov 17 '17 at 16:32
  • 2
    @Sniper Of course it does. System.currentTimeMillis() is based on the clock, whether this implementation is based on the time since the OS was startet. System.currentTimeMillis() may fail, when the OS updates its time from an NTP server (default on Android). If this timeframe falls into the gap, where your button magic happens, it may disable the button forever. This may be a one in a million, but we should avoid those traps, whenever we can. – Redwolf Nov 20 '17 at 19:05
  • @Sniper Here is an example: We click the button. mLastClickTime = System.currentTimeMillis = 23647235427. NTP service of the os sets the time to = 12347235427. We are screwd because using System.currentTimeMillis(), we need years until our buttons gets usable again. NEVER base your timeouts on clock dependend values. Use time values that begin with the start of your application or os. – Redwolf Nov 20 '17 at 19:11
  • If you are using koltin in your project here is a View extension function that do that. So you can easily use with any view inside your project. https://gist.github.com/samehgerges/d9597b5bdebf9a01122b3abfd05b6f42 – Sameh Mikhail Mar 12 '18 at 16:24
  • Something that android should provide, imho – ChRoNoN Oct 08 '18 at 19:08
  • 1
    @SamehMikhail Hi, your link doesn't work now, can you please provide correct one? Thank you. – JerabekJakub Mar 18 '19 at 09:44
  • 1
    @JerabekJakub here is an updated link https://gist.github.com/samehmikhail/d9597b5bdebf9a01122b3abfd05b6f42 – Sameh Mikhail Mar 19 '19 at 17:43
  • If you need more responsiveness and set delay to 300 ms or less, this solution may not work. I have some heavy logic after the time check in onClick and it takes 200-300 ms to compile. If user clicks on a button twice with the delay of ~100 ms, both calls gets queued, and the second is called after the first completes its computations. Thus it can take more than threshold specified and second click executes without problem. You have to optimise code inside your onClick. There's no other option – gorodechnyj Apr 24 '19 at 15:07
  • This is the only thing that worked for me! I am sending a contact form via a post request after the button is pressed, was getting two form entries with other methods. – vmetelz Jun 30 '19 at 02:40
  • Does not work. I click fast and the app crashes like before... – The incredible Jan Oct 28 '20 at 07:52
96

Disable the button with setEnabled(false) until it is safe for the user to click it again.

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • 54
    setEnabled(false) doesn't seem to work "fast" enough. I've set up a View.OnClickListener.onClick(view) for a button. First thing I do in onClick() is write a log-statement, 2nd statement is button.setEnabled(false). When fast-double-clicking in the emulator I see the log-statement appear twice! Even when adding a setClickable(false) right after that, the log-statement appears twice. – QQQuestions Jun 24 '11 at 08:41
  • Hmmm, maybe the code up to the setEnabled(true) at the end of onClick() is being executed so quick that it's already enabled again... – QQQuestions Jun 24 '11 at 09:15
  • 101
    I had a similar problem. According to one helpful gentleman, Android actually queues the clicks, so it doesnt matter how fast or slow your onClick() code executes; only how fast you click the button. Ie. You may have already disabled the button, but the click queue has already been filled with two or three clicks and disabling the button has no effect on the delivery of queued clicks. (although maybe it should?) – Nick May 22 '12 at 16:03
  • 3
    That rare moment when you see 'someone' gives an exact answer and not abstraaaaaaaact answers to the question being asked.. :P – Rahul Mar 06 '14 at 16:22
  • 1
    this is not always the right solution. If your onClick is expensive operation, then the button will not disable fast enough. The full-proof solution IMHO is to use the last click time, but also disable the button to avoid user tapping on it again if you expect the operation to last a while (such as login). – Sarang Oct 09 '14 at 17:55
  • What about using a STATIC boolean variable like, "sAllowClick", in the onClick event and always check for the variable value. If the value is "true" continue execution else do nothing ? is it a good solution ? – Jayakrishnan Jun 01 '17 at 13:31
  • @JayakrishnanPm: No. Do not use a `static` field (which is global in scope) for what is a local problem. If you want to use a *regular* field in addition to `setEanabled(false)`, you are welcome to do so. – CommonsWare Jun 01 '17 at 13:32
  • @CommonsWare what about the slowness of this method..users can hit on the button may times very quickly... still "setEnabled()" is the recommended solution for this problem? just want to finalize it. Thank you! – Jayakrishnan Jun 01 '17 at 13:35
  • @JayakrishnanPm: If you are especially concerned, use one of the timestamp approach outlined in other answers on this question. AFAIK, none of them use a `static` field. – CommonsWare Jun 01 '17 at 13:39
  • Thanks its help me – Jatin Patel Jul 07 '17 at 09:14
  • Remember that `setEnabled(false)` effectively disables the button which causes it to be rendered differently, greyed out for instance. This, in the context of the OP, is a side-effect that most likely isn't wanted. Also, if you're using this trick to e.g. guide the user though a sign up flow, then you must remember to re-enable the Button again in case the user navigates back. I'd definitely consider the timed OnClickListener mentioned in the other answer. – Stephan Henningsen Sep 27 '17 at 13:10
  • 2
    You should also use view.cancelPendingInputEvents() to prevent uncaught clicks – Maksim Turaev Jan 25 '19 at 14:13
  • 1
    check this has worked amazingly for me https://stackoverflow.com/a/22669692/5687476 – Paramjit Singh Rana Nov 11 '20 at 09:30
61

My solution is

package com.shuai.view;

import android.os.SystemClock;
import android.view.View;

/**
 * 处理快速在某个控件上双击2次(或多次)会导致onClick被触发2次(或多次)的问题
 * 通过判断2次click事件的时间间隔进行过滤
 * 
 * 子类通过实现{@link #onSingleClick}响应click事件
 */
public abstract class OnSingleClickListener implements View.OnClickListener {
    /**
     * 最短click事件的时间间隔
     */
    private static final long MIN_CLICK_INTERVAL=600;
    /**
     * 上次click的时间
     */
    private long mLastClickTime;

    /**
     * click响应函数
     * @param v The view that was clicked.
     */
    public abstract void onSingleClick(View v);

    @Override
    public final void onClick(View v) {
        long currentClickTime=SystemClock.uptimeMillis();
        long elapsedTime=currentClickTime-mLastClickTime;
        //有可能2次连击,也有可能3连击,保证mLastClickTime记录的总是上次click的时间
        mLastClickTime=currentClickTime;

        if(elapsedTime<=MIN_CLICK_INTERVAL)
            return;

        onSingleClick(v);        
    }

}

Usage is similar as OnClickListener but override onSingleClick() instead:

mTextView.setOnClickListener(new OnSingleClickListener() {
            @Override
            public void onSingleClick(View v) {
                if (DEBUG)
                    Log.i("TAG", "onclick!");
            }
     };
jinshiyi11
  • 627
  • 5
  • 4
  • 1
    Easy and perfect to automatically prevent double touches. – Benjamin Piette Jan 16 '14 at 11:27
  • 1
    This does not completely prevent double clicks if you do double click fast enough. – Loc Sep 18 '14 at 22:34
  • 1
    Great solution. I just changed MIN_CLICK_INTERVAL=1000; – Nizam Apr 02 '15 at 12:52
  • 7
    If I was to accurately button every 0.5 seconds, none of my subsequent clicks would get through because mLastClickTime would be updated each click. My suggestion would be to assign mLastClickTime after you check the interval. – k2col Jan 15 '16 at 20:15
  • mLastClickTime=currentClickTime; should placed after if(elapsedTime<=MIN_CLICK_INTERVAL) return; – Loyea Jan 28 '16 at 09:13
  • in my tests I had to add mLastClickTime = SystemClock.uptimeMillis() at the end of this method because if my onSingleClick takes too long to complete it was triggered more than once – digulino Oct 02 '17 at 12:13
  • Btw answerer, please provide an English version of the comments in the code such that others can better understand what your code does instead of plainly copying and pasting your code. – Edric Sep 16 '19 at 15:24
  • This is also a good solution if you have more than 1 button and each button starts an intent, so when the user touches 2 or more buttons quickly only the first button will be triggered. – stramin Nov 06 '19 at 15:20
  • That solution works for me, but how to implement this in XML Code -> android:onClick ? – Nao Kreuzeder May 21 '20 at 17:57
44

Disabling the button or setting unclickable is not enough if you are doing computationally intensive work in onClick() since click events can get queued up before the button can be disabled. I wrote an abstract base class that implements OnClickListener that you can override instead, that fixes this problem by ignoring any queued up clicks:

/** 
 * This class allows a single click and prevents multiple clicks on
 * the same button in rapid succession. Setting unclickable is not enough
 * because click events may still be queued up.
 * 
 * Override onOneClick() to handle single clicks. Call reset() when you want to
 * accept another click.
 */
public abstract class OnOneOffClickListener implements OnClickListener {
    private boolean clickable = true;

    /**
     * Override onOneClick() instead.
     */
    @Override
    public final void onClick(View v) {
        if (clickable) {
            clickable = false;
            onOneClick(v);
            //reset(); // uncomment this line to reset automatically
        }
    }

    /**
     * Override this function to handle clicks.
     * reset() must be called after each click for this function to be called
     * again.
     * @param v
     */
    public abstract void onOneClick(View v);

    /**
     * Allows another click.
     */
    public void reset() {
        clickable = true;
    }
}

Usage is same as OnClickListener but override OnOneClick() instead:

OnOneOffClickListener clickListener = new OnOneOffClickListener() {
    @Override
    public void onOneClick(View v) {

        // Do stuff

        this.reset(); // or you can reset somewhere else with clickListener.reset();
    }
};
myButton.setOnClickListener(clickListener);
Ken
  • 2,758
  • 1
  • 21
  • 28
  • Thanks for the code. Based on what you've written here I've made similar class that prevents clicks if button or it's parent is hidden - it's funny that Android will queue clicks even if you hide button immediately after click. – nikib3ro Jul 29 '12 at 09:33
  • 1
    I have a similar solution - you can just use this class in xml and it will wrap the clickListeners for you https://github.com/doridori/AndroidUtilDump/blob/master/android/src/couk/doridori/android/lib/view/SafeButton.java – Dori Sep 26 '12 at 11:42
  • 3
    This is a good solution, but onClick() and reset() need to be synchronized, otherwise a double-click can still sneak in. – Tom anMoney Nov 05 '13 at 08:52
  • 7
    @TomanMoney, how? Don't all click events happen on a single UI thread? – Ken Mar 17 '15 at 20:46
  • @Dori the link is dead – naXa Sep 08 '18 at 16:11
  • @Ken I guess Tom's opinion is users might call `reset()` method from a background thread, if this value is not updated in the main thread, then the users cannot click the button (although maybe it should). – Son Truong Jan 22 '21 at 08:34
39

You can do it in very fancy way with Kotlin Extension Functions and RxBinding

   fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit): Disposable =
        RxView.clicks(this)
                .debounce(debounceTime, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { action() }

or

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()
        }
    })
}

and then just:

View.clickWithDebounce{ Your code }
Yvgen
  • 1,461
  • 13
  • 24
14

I also run in similar problem , I was displaying some datepicker & timepickers where sometimes it got click 2 times. I have solved it by this

long TIME = 1 * 1000;
@Override
public void onClick(final View v) {
v.setEnabled(false);

    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            v.setEnabled(true);
        }
    }, TIME);
}

You can change time depending upon your requirement. This method work for me.

Mudassar
  • 1,468
  • 4
  • 20
  • 29
  • This is the real solution, since it reactivates the button automatically after the time has elapsed. The other solutions block the button permanently if you click twice quickly. This solution fixes the problem! Thanks – Néstor May 09 '20 at 10:03
13

The KLEANEST Kotlin idiomatic way:

class OnSingleClickListener(private val block: () -> Unit) : View.OnClickListener {

    private var lastClickTime = 0L

    override fun onClick(view: View) {
        if (SystemClock.elapsedRealtime() - lastClickTime < 1000) {
            return
        }
        lastClickTime = SystemClock.elapsedRealtime()

        block()
    }
}

fun View.setOnSingleClickListener(block: () -> Unit) {
    setOnClickListener(OnSingleClickListener(block))
}

Usage:

button.setOnSingleClickListener { ... }
WindRider
  • 10,514
  • 5
  • 46
  • 55
11

I know it's an old question, but I share the best solution I found to solve this common problem

        btnSomeButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Prevent Two Click
            Utils.preventTwoClick(view);
            // Do magic
        }
    });

And in another file,like Utils.java

    /**
 * Método para prevenir doble click en un elemento
 * @param view
 */
public static void preventTwoClick(final View view){
    view.setEnabled(false);
    view.postDelayed(new Runnable() {
        public void run() {
           view.setEnabled(true);
        }
    }, 500);
}
gustavo
  • 107
  • 2
  • 6
9

setEnabled(false) works perfectly for me.

The idea is I write { setEnabled(true); } in the beginning and just make it false on the first click of the button.

eckes
  • 56,506
  • 25
  • 151
  • 189
saurabh
  • 611
  • 1
  • 7
  • 14
8

If someone is still looking for a short answer you can use the below code

 private static long mLastClickTime = 0;
  if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) { // 1000 = 1second
         return;
    }
 mLastClickTime = SystemClock.elapsedRealtime();

This code will go inside the if statement whenever the user clicks on the View within 1 second and then the return; will be initiated and the further code will not initiate.

Rakshit Nawani
  • 2,490
  • 12
  • 25
8

The actual solution to this problem is to use setEnabled(false) which greys out the button, and setClickable(false) which makes it so the second click can not be received I have tested this and it seem to be very effective.

EpicOfChaos
  • 802
  • 11
  • 22
6

My solution is try to using a boolean variable :

public class Blocker {
    private static final int DEFAULT_BLOCK_TIME = 1000;
    private boolean mIsBlockClick;

    /**
     * Block any event occurs in 1000 millisecond to prevent spam action
     * @return false if not in block state, otherwise return true.
     */
    public boolean block(int blockInMillis) {
        if (!mIsBlockClick) {
            mIsBlockClick = true;
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    mIsBlockClick = false;
                }
            }, blockInMillis);
            return false;
        }
        return true;
    }

    public boolean block() {
        return block(DEFAULT_BLOCK_TIME);
    }
}

And using as below:

view.setOnClickListener(new View.OnClickListener() {
            private Blocker mBlocker = new Blocker();

            @Override
            public void onClick(View v) {
                if (!mBlocker.block(block-Time-In-Millis)) {
                    // do your action   
                }
            }
        });

UPDATE: Kotlin solution, using view extension

fun View.safeClick(listener: View.OnClickListener, blockInMillis: Long = 500) {
    var lastClickTime: Long = 0
    this.setOnClickListener {
        if (SystemClock.elapsedRealtime() - lastClickTime < blockInMillis) return@setOnClickListener
        lastClickTime = SystemClock.elapsedRealtime()
        listener.onClick(this)
    }
}
GianhTran
  • 2,890
  • 2
  • 15
  • 34
6

In kotlin

button.setOnClickListener { 
    it?.apply { isEnabled = false; postDelayed({ isEnabled = true }, 400) } //400 ms
    //do your work
}
Nishant
  • 31,137
  • 4
  • 36
  • 50
5

in my situation i was using a button view and it was taking the clicks too quickly. just disable the clickable and enable it again after a few seconds...

Basically i made a wrapper class that wraps around your Views onClickListener. you can also set a custom delay if you want.

public class OnClickRateLimitedDecoratedListener implements View.OnClickListener {

    private final static int CLICK_DELAY_DEFAULT = 300;
    private View.OnClickListener onClickListener;
    private int mClickDelay;


        public OnClickRateLimitedDecoratedListener(View.OnClickListener onClickListener) {
            this(onClickListener, CLICK_DELAY_DEFAULT);
        }

        //customize your own delay
        public OnClickRateLimitedDecoratedListener(View.OnClickListener onClickListener, int delay) {
            this.onClickListener = onClickListener;
            mClickDelay = delay;
        }

        @Override
        public void onClick(final View v) {
            v.setClickable(false);
            onClickListener.onClick(v);

            v.postDelayed(new Runnable() {
                @Override
                public void run() {
                    v.setClickable(true);
                }
            }, mClickDelay);
        }
    }

and to call it simply do this:

mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 doSomething();
             }
         }));

or provide your own delay:

 mMyButton.setOnClickListener(new OnClickRateLimitedDecoratedListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         doSomething();
                     }
                 },1000));

UPDATE: Above ways a little old fashion now that RxJava is so prevalent. as others have mentioned, in android we could use a throttle to slow down the clicks. here is one example:

 RxView.clicks(myButton)
                    .throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                    .subscribe {
                        Log.d("i got delayed clicked")
                    }
        }

you can use this library for it: implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'

j2emanue
  • 51,417
  • 46
  • 239
  • 380
5

Click Guard works well with Butter Knife

ClickGuard.guard(mPlayButton);
thanhbinh84
  • 14,604
  • 4
  • 46
  • 59
5

We could use the button just synchronized like:

Example #1 (Java)

@Override
public void onClick(final View view) {
    synchronized (view) {

        view.setEnabled(false);

        switch (view.getId()) {
            case R.id.id1:
                ...
                break;
            case R.id.id2:
                ...
                break;
                ...
        }

        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                view.setEnabled(true);
            }
        }, 1000);
    }
}

Example #2 (kotlin) using synchronized

myButton.setOnClickListener { view ->
            synchronized(view) {
                view.isEnabled = false

                // do something

                view.postDelayed({ view.isEnabled = true }, 500L)
            }
        }

Good Luck)

lasec0203
  • 1,784
  • 16
  • 32
Hovanes Mosoyan
  • 742
  • 7
  • 9
5

Below code will prevent user to click multiple times within a fractions of seconds and allow only after 3 seconds.

private long lastClickTime = 0;

View.OnClickListener buttonHandler = new View.OnClickListener() {
    public void onClick(View v) {
        // preventing double, using threshold of 3000 ms
        if (SystemClock.elapsedRealtime() - lastClickTime < 3000){
            return;
        }

        lastClickTime = SystemClock.elapsedRealtime();
    }
}
Milan Sheth
  • 804
  • 12
  • 10
4
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            //to prevent double click
            button.setOnClickListener(null);
        }
    });
Indrek Kõue
  • 6,068
  • 7
  • 32
  • 66
4

I found none of these suggestions works if the onClick method doesn't return immediately. The touch event is queued by Android and the next onClick is called only after the first one is finished. (Since this is done on the one UI thread this is really normal.) I needed to use the time when the the onClick function is finished + one boolean variable to mark whether the given onClick is running. Both these marker attributes are static to avoid any onClickListener to run at the same time. (If user clicks on another button) You can simple replace your OnClickListener to this class and instead of implementing the onClick method you need to implement the abstract oneClick() method.

    abstract public class OneClickListener implements OnClickListener {

    private static boolean started = false;
    private static long lastClickEndTime = 0;

    /* (non-Javadoc)
     * @see android.view.View.OnClickListener#onClick(android.view.View)
     */
    @Override
    final public void onClick(final View v) {
        if(started || SystemClock.elapsedRealtime()-lastClickEndTime <1000 ){
            Log.d(OneClickListener.class.toString(), "Rejected double click, " + new Date().toString() );
            return; 
        }
        Log.d(OneClickListener.class.toString(), "One click, start: " + new Date().toString() );
        try{
            started = true;
            oneClick(v);
        }finally{
            started = false;
            lastClickEndTime = SystemClock.elapsedRealtime();
            Log.d(OneClickListener.class.toString(), "One click, end: " + new Date().toString() );
        }
    }

    abstract protected void oneClick(View v);
}
linczy
  • 131
  • 1
  • 4
4

You can use this method. By using post delay you can take care for double click events.

void debounceEffectForClick(View view) {

    view.setClickable(false);

    view.postDelayed(new Runnable() {
        @Override
        public void run() {
            view.setClickable(true);

        }
    }, 500);
}
manmohan
  • 370
  • 2
  • 13
4

Kotlin extension that allows for concise inline code & variable double click wait times

fun View.setDoubleClickListener(listener: View.OnClickListener, waitMillis : Long = 1000) {
    var lastClickTime = 0L
    setOnClickListener { view ->
        if (System.currentTimeMillis() > lastClickTime + waitMillis) {
            listener.onClick(view)
            lastClickTime = System.currentTimeMillis()
        }
    }
}

Usage:

anyView.setNoDoubleClickListener(View.OnClickListener { v ->
    // do stuff
})

Or

anyView.setNoDoubleClickListener(View.OnClickListener { v ->
    // do stuff
}, 1500)
Jim
  • 546
  • 4
  • 8
3

For me only remembering timestamp and checking against it (that more than 1 sec passed since previous click) helped.

shtolik
  • 1,145
  • 21
  • 26
3

I hope this can help YOU, put the code in you event handler.

// --------------------------------------------------------------------------------

    boolean hasTag = null != which.getTag( R.id.preventing_double_click_tag );

    if ( hasTag ) {
        // Do not handle again...
        return;
    } else {
        which.setTag( R.id.action, Boolean.TRUE );

        which.postDelayed( new Runnable() {
            @Override
            public void run() {
                which.setTag( R.id.action, null );
                Log.d( "onActin", " The preventing double click tag was removed." );
            }

        }, 2000 );
    }
ONG
  • 31
  • 1
  • 3
3

you can also use rx bindings by jake wharton to accomplish this. here is a sample that pads 2 seconds between successive clicks:

RxView.clicks(btnSave)
                .throttleFirst(2000, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept( Object v) throws Exception {
//handle onclick event here
                });

//note: ignore the Object v in this case and i think always.

j2emanue
  • 51,417
  • 46
  • 239
  • 380
3

Kotlin create class SafeClickListener

class SafeClickListener(
        private var defaultInterval: Int = 1000,
        private val onSafeCLick: (View) -> Unit
) : View.OnClickListener {
    private var lastTimeClicked: Long = 0    override fun onClick(v: View) {
        if (SystemClock.elapsedRealtime() - lastTimeClicked < defaultInterval) {
            return
        }
        lastTimeClicked = SystemClock.elapsedRealtime()
        onSafeCLick(v)
    }
}

create a function in baseClass or else

fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {val safeClickListener = SafeClickListener {
        onSafeClick(it)
    }
    setOnClickListener(safeClickListener)
}

and use on button click

btnSubmit.setSafeOnClickListener {
    showSettingsScreen()
}
jai khambhayta
  • 2,263
  • 12
  • 24
3

Adding to Jim's answer the code can be made more concise:

fun View.setOnSingleClick(onClick: () -> Unit) {
    var lastClickTime = 0L
    setOnClickListener {
        if (currentTimeMillis() > lastClickTime + 750) onClick()
        lastClickTime = currentTimeMillis()
    } 
}

Usage:

aView.setOnSingleClick {  }
hamrosvet
  • 233
  • 3
  • 8
3

It seems that setting your click listeners in onResume and nulling them out in onPause does the trick too.

khendricks
  • 538
  • 5
  • 13
2

I fix this problem using two clases, one similar to @jinshiyi11 answer's and the anoter is based on explicit click, in this you can click a button only once time, if you want another click you have to indicate it explicitly.

/**
 * Listener que sólo permite hacer click una vez, para poder hacer click
 * posteriormente se necesita indicar explicitamente.
 *
 * @author iberck
 */
public abstract class OnExplicitClickListener implements View.OnClickListener {

    // you can perform a click only once time
    private boolean canClick = true;

    @Override
    public synchronized void onClick(View v) {
        if (canClick) {
            canClick = false;
            onOneClick(v);
        }
    }

    public abstract void onOneClick(View v);

    public synchronized void enableClick() {
        canClick = true;
    }

    public synchronized void disableClick() {
        canClick = false;
    }
}

Example of use:

OnExplicitClickListener clickListener = new OnExplicitClickListener() {
    public void onOneClick(View v) {
        Log.d("example", "explicit click");
        ...
        clickListener.enableClick();    
    }
}
button.setOnClickListener(clickListener);
iberck
  • 2,162
  • 5
  • 30
  • 37
2
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {

    private final AtomicBoolean onClickEnabled = new AtomicBoolean(true);

    @Override
    public void onClick(View v) {
        Log.i("TAG", "onClick begin");
        if (!onClickEnabled.compareAndSet(true, false)) {
            Log.i("TAG", "onClick not enabled");
            return;
        }

        button.setEnabled(false);

        // your action here

        button.setEnabled(true);
        onClickEnabled.set(true);
        Log.i("TAG", "onClick end");
    }
});
Milan Hlinák
  • 3,295
  • 1
  • 27
  • 40
  • View listeners are always called on a single thread (main) so using `AtomicBoolean` could not really help. – WindRider Sep 16 '19 at 15:12
2

Try this, it is working:

mButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {

                mSlotLayout.setEnabled(false);

        //      do your work here

                Timer buttonTimer = new Timer();
                buttonTimer.schedule(new TimerTask() {

                    @Override
                    public void run() {

                        runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                mButton.setEnabled(true);
                            }
                        });
                    }
                }, 500); // delay button enable for 0.5 sec
    }
});
rupinderjeet
  • 2,597
  • 24
  • 44
King of Masses
  • 16,881
  • 4
  • 57
  • 75
2

My solution (Kotlin):

class OnDebouncedClickListener(private val delayInMilliSeconds: Long, val action: () -> Unit) : View.OnClickListener {
    var enable = true

    override fun onClick(view: View?) {
        if (enable) {
            enable = false
            view?.postDelayed(delayInMilliSeconds) { enable = true }
            action()
        }
    }
}

fun View.setOnDebouncedClickListener(delayInMilliSeconds: Long = 500, action: () -> Unit) {
    val onDebouncedClickListener = OnDebouncedClickListener(delayInMilliSeconds, action)
    setOnClickListener(onDebouncedClickListener)
}

Use :

button.apply {       
            setOnDebouncedClickListener {
                //your action on click
            }
        }
VinceMedi
  • 170
  • 8
2

There is a native debounce click listener in Java

view.setOnClickListener(new DebouncedOnClickListener(1000) { //in milisecs
            @Override
            public void onDebouncedClick(View v) {
                //action
            }
        });
2

This solution (Kotlin) works on me:

abstract class SingleClickListener : View.OnClickListener {
    private val MIN_CLICK_INTERVAL: Long = 1000
    private var mLastClickTime: Long = 0

    abstract fun onSingleClick(v: View?)

    override fun onClick(v: View?) {
        if (mLastClickTime <= 0) {
            mLastClickTime = SystemClock.uptimeMillis()
            onSingleClick(v)
            return
        }

        if (SystemClock.uptimeMillis() - mLastClickTime <= MIN_CLICK_INTERVAL) {
            return
        }

        mLastClickTime = SystemClock.uptimeMillis()

        onSingleClick(v)
    }
}

Usage:

someView.setOnClickListener(object : SingleClickListener() {
    override fun onSingleClick(v: View?) {
        v?.also { klik(it) }
    }
})

Or also create extension function for adding ClickListener on view easily:

fun View.click(klik: (View) -> Unit) {
    this.setOnClickListener(object : SingleClickListener() {
        override fun onSingleClick(v: View?) {
            v?.also { klik(it) }
        }
    })
}

// Usage
class XPerimentActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_xperiment_layout)

        submit_button.click {
           // do your magic
        }
    }
}
midblurz
  • 29
  • 5
2

Setting Clickable to false does not work on the first double click but subsequent double clicks are blocked. It is as though the loading click delegate the first time is slower and the second click is captured before the first completes.

        Button button = contentView.FindViewById<Button>(Resource.Id.buttonIssue);
        button.Clickable = false;
        IssueSelectedItems();
        button.Clickable = true;
KawBoy
  • 931
  • 1
  • 7
  • 9
1

Only 2 step , and you can use it everywhere in your application.

Step1. create a singleton to manager [avoiding multiple click]

package com.im.av.mediator;

import android.os.SystemClock;

import java.util.HashMap;

/**
 * Created by ShuHeng on 16/6/1.
 */
public class ClickManager {



    private HashMap<Integer,Long> laskClickTimeMap=new HashMap<Integer,Long>();
    public volatile static ClickManager mInstance =null;

    public static ClickManager getInstance(){
        if (mInstance == null) {
            synchronized(ClickManager.class) {
                if (mInstance == null) {
                    mInstance = new ClickManager();
                }
            }
        }
        return mInstance;
    }
   public boolean isClickable1s(Integer key){
       Long keyLong = laskClickTimeMap.get(key);
       if(keyLong==null){
           laskClickTimeMap.put(key,SystemClock.elapsedRealtime());
           return true;
       }else{
           if (SystemClock.elapsedRealtime() - keyLong.longValue() < 1000){
               return false;
           }else{
               laskClickTimeMap.put(key,new Long(SystemClock.elapsedRealtime()));
               return true;
           }
       }
   }
}  

Step2. add one line to avoid multiple click.

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub
    int id = v.getId();
    if (id == R.id.iv_back) {
        if(!ClickManager.getInstance().isClickable1s(R.id.iv_back))return;
        //do something
    } else if (id == R.id.iv_light) {
        if(!ClickManager.getInstance().isClickable1s(R.id.iv_light))return;
        //do something
    } else if (id == R.id.iv_camerarotate) {
        if(!ClickManager.getInstance().isClickable1s(R.id.iv_camerarotate))return;
           //do something
    } else if (id == R.id.btn_delete_last_clip) {
        if(!ClickManager.getInstance().isClickable1s(R.id.btn_delete_last_clip))return;
          //do something

    } else if (id == R.id.iv_ok) {
        if(!ClickManager.getInstance().isClickable1s(R.id.iv_ok))return;
        //do something
    }
}
shuheng
  • 11
  • 1
1

With Kotlin extension function :

fun View.onSingleClick(action: (v: View) -> Unit) {
    setOnClickListener(object : View.OnClickListener {
        override fun onClick(v: View) {
            isEnabled = false
            action(v)
            postDelayed({ isEnabled = true }, 700)
        }
    })
}

usage:

button.onSingleClick { myAction() }
ali jabbari
  • 11
  • 1
  • 2
0

If you don't want (or can not) use boolean flags or override onClickListener, you also can try to declare your activity with android:launchMode="singleTop" in AndroidManifest.xml.

How it's work?

  • If an instance of the activity is at the top of the stack – new activity will not be create, instead – onNewIntent() will be called.
  • Activity can have multiple instances
  • Instances can reside in different tasks
  • One task can have multiple instances
Community
  • 1
  • 1
Alex Dzeshko
  • 1,045
  • 8
  • 8
0

I prefer to use a semaphore block. It is thread-safe and can be used not only for buttons.

The code sample is simple:

private UtilsSemaphore buttonSemaphore = new UtilsSemaphore();

public void onClick(View view)
{

    boolean isAllowed = buttonSemaphore.lock();

    if(!isAllowed)
    {
        return;
    }

    final View clickedButton = view;

    clickedButton.setEnabled(false);

    /* some code */

    buttonSemaphore.unlock();
    clickedButton.setEnabled(true);
}


public class UtilsSemaphore {

    public int counter = 0;

    public boolean lock()
    {
        int counterValue = ++counter;
        boolean isAllowed = counterValue < 2;

        if(!isAllowed)
        {
            unlock();
        }

        return isAllowed;
    }

    public void unlock()
    {
        --counter;
    }

}
Andrey Hohutkin
  • 1,924
  • 1
  • 13
  • 17
0

If the only thing the button do is launching a new activity the problem could be solved with "singleTop" activiy launch mode and FLAG_ACTIVITY_CLEAR_TOP being set on the intent. This one will not work in the case of complex activity's ierarchy but sutable for simple tree-like application structure.

Denis Gladkiy
  • 1,823
  • 1
  • 23
  • 37
0

Generic Solution

@Override
        public void onClick(View v) {
            tempDisableButton(v);
            //all the buttons view..
        }

public void tempDisableButton(View viewBtn) {
        final View button = viewBtn;

        button.setEnabled(false);
        button.postDelayed(new Runnable() {
            @Override
            public void run() {
                button.setEnabled(true);
            }
        }, 3000);
    }
asok Buzz
  • 1,706
  • 3
  • 22
  • 49
0

I needed that woking with fragments and just put a flag to controll the clicks: I only want the first one, the others can't access to the listener

private boolean flag = true;

...

@Override
public void onClick(View view) {

    ...

    if (flag) {

        ...

        listener.onFragmentInteraction(Constants.MY_FRAGMENT, bundle);
        flag = false;
    }

    ...

}

Hope it will useful, and correct me if it is not correct

Jaco
  • 758
  • 2
  • 11
  • 25
0

Click events queue up when the UI thread is blocked. On a button click event, change to a background task as soon as possible to avoid click events queueing up behind each other.

Declare a volatile boolean or lock inside the activity class:

private volatile boolean saving = false;

Create a Button with an onClickListener that is gated by saving and start a background task to do the work:

saveButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View view) {
        if (!saving) {
            saving = true;
            new SaveAsyncTask().execute();
        }
    }
});

Create an inner SaveAsyncTask class to do the work in the background:

class SaveAsyncTask extends AsyncTask {

    @Override
    protected Object doInBackground(Object[] objects) {
        // Do something here, simulate a 3 second task
        SystemClock.sleep(3000);
        saving = false;
        return null;
    }
}
Gary Davies
  • 814
  • 13
  • 11
0

This is my solution:

if (waitDouble) {
    waitDouble = false;
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                sleep(300);
                if (waitDouble == false) {
                    waitDouble = true;
                    singleClick();  //singleClick
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
} else {//DoubleClick
    DoubleClick();
    waitDouble = true;
}

Or another solution:

public class NoDoubleClickUtils {
    private static long lastClickTime;
    private final static int SPACE_TIME = 500;

    public static void initLastClickTime() {
        lastClickTime = 0;
    }

    public synchronized static boolean isDoubleClick() {
        long currentTime = System.currentTimeMillis();
        boolean isClick2;
        if (currentTime - lastClickTime > SPACE_TIME) {
            isClick2 = false;
        } else {
            isClick2 = true;
        }
        lastClickTime = currentTime;
        return isClick2;
    }
}
KongJing
  • 465
  • 5
  • 7
0

If on click of the button, you're opening a new fragment, just add android:clickable="true" to the root view of the new fragment being opened.

nipun.birla
  • 721
  • 5
  • 18
0
The Best and simple solution i found is 
1. to create a boolean and set as false (default) like
private boolean itemClicked = false;

/* for a safer side you can also declare boolean false in onCreate() also. */
and at onclick() method check 
2. if(!itemClicked)
{
itemClicked = true;
// rest of your coding functionality goes here of onClick method.
}
3. last step is to set boolean false in onResume()
@override
onResume()
{
super.onResume(0);
itemClicked = false;
}
0

for any one using data-binding :

@BindingAdapter("onClickWithDebounce")
fun onClickWithDebounce(view: View, listener: android.view.View.OnClickListener) {
    view.setClickWithDebounce {
        listener.onClick(view)
    }
}

object LastClickTimeSingleton {
    var lastClickTime: Long = 0
}

fun View.setClickWithDebounce(action: () -> Unit) {
    setOnClickListener(object : View.OnClickListener {

        override fun onClick(v: View) {
            if (SystemClock.elapsedRealtime() - LastClickTimeSingleton.lastClickTime < 500L) return
            else action()
            LastClickTimeSingleton.lastClickTime = SystemClock.elapsedRealtime()
        }
    })
}



<androidx.appcompat.widget.AppCompatButton
                    ..
  android:text="@string/signup_signin"
  app:onClickWithDebounce="@{() -> viewModel.onSignUpClicked()}"
                   ... />
Salah Hammouda
  • 233
  • 2
  • 15
0

Setting the button as clickable false upon clicking and true once it is desired to make the button clickable again is the right approach. For instance, consider the following scenario: you are making a service call upon click of a button and once the service is done you want to display a dialog. For this, once the button is clicked you can set setClickable(false) and once the service responds you will do setClicklable(true) through a reference you pass to your custom dialog. When dialog invokes isShowing() you can trigger the listener and setClicklable(true).

HaroldSer
  • 1,724
  • 2
  • 9
  • 17
0

This solution is quick and neat.

basically you just prevent double touch from the base styles in your app and implement standard on click listener. This works like a charm with touches at the same time of different views.

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
...
<item name="android:splitMotionEvents">false</item>
<item name="android:windowEnableSplitTouch">false</item>
</style>

If your app uses some kind of gesture event this is not the right answer.

alexm
  • 1,154
  • 14
  • 32
0

Try this Kotlin extension function :

private var lastClickTime = 0L

fun View.click(action: () -> Unit) {
    setOnClickListener {
        if (SystemClock.elapsedRealtime() - lastClickTime < 600L)
            return@setOnClickListener
        lastClickTime = SystemClock.elapsedRealtime()
        action()
    }
}

It prevent also clicking in various parts of the app at the same time.

Jéwôm'
  • 3,202
  • 3
  • 28
  • 62
0

Here's a OnClickListener proxy that prevents successive clicks, based on qezt's answer.

import android.os.SystemClock;
import android.view.View;

public class MultiClickGuard implements View.OnClickListener {    

    private long mLastClickTime;

    private final int mThresholdMillis;
    private final View.OnClickListener mListener;

    public MultiClickGuard(View.OnClickListener listener, int thresholdMillis) {
        if (listener == null) {
            throw new NullPointerException("Null OnClickListener");
        }
        if (thresholdMillis < 0) {
            throw new IllegalArgumentException("Negative click threshold: " + thresholdMillis);
        }

        mListener = listener;
        mThresholdMillis = thresholdMillis;
    }

    @Override
    public void onClick(View v) {
        // Using a time threshold to prevent successive clicks.
        if (SystemClock.elapsedRealtime() - mLastClickTime < mThresholdMillis) {
            return;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        // Forward the click event to the *real* listener.
        mListener.onClick(v);
    }
}

Usage examples

button.setOnClickListener(new MultiClickGuard(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // do something
    }
}, 1000));

button.setOnClickListener(new MultiClickGuard(v -> {...}, 1000));
button.setOnClickListener(new MultiClickGuard(v -> doSomething(), 1000));

In situations where you're trying to prevent starting multiple instances of an activity, consider specifying the launch mode: Understand Tasks and Back Stack, which is the reliable way to do it.

If you're trying to prevent opening multiple instances of a dialog fragment, you can check if the fragment manager already contains the dialog, e.g. getSupportFragmentManager().findFragmentByTag(tag).

Nick Dandoulakis
  • 40,422
  • 14
  • 98
  • 136
0

Prevents click on multiply btns

Using:

private val disposables = CompositeDisposable()
private val clickInteractor = ClickInteractor(disposables)
...
button1.setOnClickListener{
     clickInteractor.click {
          Toast.makeText(context, "Btn1", Toast.LENGTH_LONG).show()
     }
}
button2.setOnClickListener{
     clickInteractor.click {
          Toast.makeText(context, "Btn2", Toast.LENGTH_LONG).show()
     }
}

ClickInteractor.kt:

class ClickInteractor constructor(disposables: CompositeDisposable) {
    private val performPublish = PublishSubject.create<ClickInteractorCallback>()

    init {
        performPublish
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .throttleFirst(1, TimeUnit.SECONDS, Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnNext { callback ->
                callback.invoke()
            }
            .retry()
            .execute(disposables)
    }

    fun click(callback: ClickInteractorCallback) {
        performPublish.onNext(callback)
    }
}

typealias ClickInteractorCallback = () -> Unit
NickUnuchek
  • 8,369
  • 9
  • 74
  • 111
-3

more preferred solution is,

onclick(){
  btn.setEnabled(false);
  btn.setClickable(false);
  //yourwork
  myWork();
}

myWork(){
 //your tasks.
 btn.setEnabled(true);
 btn.setClickable(true);
}

As a link can be ignored easily, I had to repeat this again and again

Narasimha
  • 537
  • 4
  • 7
  • This doesn't solve the double click issue because Android can still queue up clicks before the onClickListener has a chance to fire. – ashishduh Jan 27 '14 at 23:36