(English) Preventing Multiple Click

Není k dispozici v češtině.

There are situations where clicking on view triggers some action that takes more time. For example, saving data to the database or updating data from the network. In this case, it is good (and in case of network calls mandatory) to perform these operations on background thread.
But how to prevent user to click multiple times and trigger multiple operations? There are some options to consider.

Show dialog

You can show an uncancelable dialog that will prevent unwanted clicks until it is dismissed. For example show this progress dialog with spinning wheel and hide it when the operation finishes.

private void onOptionAClicked() {

mDialog = new ProgressDialog(context);
mDialog.setIndeterminate(true);
mDialog.setMessage(context.getString(R.string.please_wait));
mDialog.setCancelable(true);
mDialog.setCanceledOnTouchOutside(false);
mDialog.show();

new PerformOperationTask(this, new OperationFinishedListener() {
	mDialog.dismiss();
}).execute();
}

Set flag

Another option is to set the Boolean flag when the operation is started and ignore all clicks when the flag is true. This will be hidden to the user and might be good to use when showing dialog would be too disturbing.

private boolean mIsSaving;
public void onClick(View v) {
	if (!mIsSaving) {
		mIsSaving = true
	new PerformOperationTask(this, new OperationFinishedListener() {	
		mIsSaving = false;
	}).execute();
	}
}

You might ask why am I not simply calling view.setEnabled(false) before task and view.setEnabled(true) when task finishes. Or setClickable. It is because Android queues UI changes and it simply might not be fast enough. And you can test that it won’t be.

Set timer

If you know how much time operation will approximately take and you don’t need to use a callback, it might be possible to set a timer instead of setting operation callback. Also, this can be used with the Boolean flag.

public void onClick(View v) {
	if (!mIsSaving) {
		mIsSaving = true
		new PerformOperationTask(this).execute();
		new Handler().postDelayed(new Runnable() {

        		@Override
        		public void run() {
            		mIsSaving
        		}
    		}, 1000);

	}
}

Check time of the last click

This is similar to Boolean flag approach but is setting time of the last click instead. An operation will be performed only after some delay since last successful click.

private long mLastClickInMillis;
private static final long MINIMUM_INTERVAL_IN_MILLIS = 1000;

public void onClick(View v) {
	long time = System.currentTimeMillis();
	if (time -  MINIMUM_INTERVAL_IN_MILLIS >  mLastClickInMillis) {
		mLastClickInMillis = time;
		new PerformOperationTask(this).execute();
	}
}

Check time of last click elegantly

Both previous approaches to check time work but the code is not really nice, elegant and clean. There are too many lines for single click and if you need to prevent multiple clicks on multiple views, there will be many variables. You can achieve same functionality without setting and checking any flags inside your activity or fragment code.
Simply create abstract class View.OnClickListener and put all logic inside. OnClick method will perform the check and call another abstract method if click handling is allowed. It can look like this:

public abstract class SingleClickListener implements View.OnClickListener {
    private static final int DELAY = 1000; 
    public long lastClickTime = 0;
    public abstract void onSingleClick(View view);
    
@Override
    public void onClick(View v) {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastClickTime > DELAY) {
            lastClickTime = currentTime;
            onSingleClick(v);
        }
    }
}

And use this listener like this:

view.setOnClickListener(new SingleClickListener() {
    @Override
    public void onSingleClick(View view) {
        new PerformOperationTask(this).execute();
    }
});