1

A common problem in android apps for me seems to be, that click events can be triggered multiple times, when they should not.

I am using Butterknife - consider the following example

@OnClick(R.id.button_foto_aufnehmen)
protected void takePicture() {
  m_camera.takePicture();
}

with a camera app with some layout containing

    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    ...

    <ImageButton
        android:id="@+id/button_foto_aufnehmen"
        .../>

Now it should not be possible to click the button again, until at least after the onPictureTaken callback has completed - or even later.

Another example would be

I really don't want to manually introduce a boolean flag every time this comes up. I'd think of something like an annotation that allows me to specify an event that resets a boolean which is generated automatically. Anything but boilerplate code please.

So what is the best practice here (particularly keeping in mind the usage of butter knife)?

IARI
  • 1,217
  • 1
  • 18
  • 35
  • I think the the only and the best solution is use a boolean flag. – adalpari Jul 21 '16 at 11:37
  • Please post your XML. – frogatto Jul 21 '16 at 11:53
  • i believe using a boolean flag every time this comes up manually is a horrible idea. I'd prefer to have an annotation that does the boolena flag stuff automatically. Do you mean the XML layout? thats rather big - i would have to make a tiny example - is there a way to solve it via XML? – IARI Jul 21 '16 at 12:11
  • Possible duplicate of [How to prevent double code running by clicking twice fast to a button in Android](https://stackoverflow.com/questions/11290610/how-to-prevent-double-code-running-by-clicking-twice-fast-to-a-button-in-android) – Simon K. Gerges Oct 03 '18 at 12:09

3 Answers3

4

The above solution is very good. But it will become even better when using the power of Kotlin

1) Create the SafeClikc Listener

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

2) Add extension function to make it works with any view, this will create a new SafeClickListener and delegate the work to it

fun View.setSafeOnClickListener(onSafeClick: (View) -> Unit) {

    val safeClickListener = SafeClickListener {
        onSafeClick(it)
    }
    setOnClickListener(safeClickListener)
}

3) Now it is very easy to use it

settingsButton.setSafeOnClickListener {
    showSettingsScreen()
}

Happy Kotlin ;)

Simon K. Gerges
  • 3,097
  • 36
  • 34
2

I have faced this situation before. Basically I ended up implementing my own CustomClickListener and setting a threshold of a specific time interval (for instance, 1000ms) before allowing firing the trigger. Basically when you click the button and trigger the listener, it will check if the trigger was executed in the past amount of time you defined, and if not it will trigger it.

public class CustomClickListener implements View.OnClickListener {

    protected int defaultInterval;
    private long lastTimeClicked = 0;

    public CustomClickListener() {
        this(1000);
    }

    public CustomClickListener(int minInterval) {
        this.defaultInterval = minInterval;
    }

    @Override
    public void onClick(View v) {
        if (SystemClock.elapsedRealtime() - lastTimeClicked < defaultInterval) {
            return;
        }
        lastTimeClicked = SystemClock.elapsedRealtime();
        performClick(v);
    }

     public abstract void performClick(View v);

}

And in your class, you can do the following without jeopardizing your implementation (normal code)

myButton = (Button) findViewById(R.id.my_button);
myButton.setOnClickListener(new CustomClickListener() {
    @Override
    public void performClick(View view) {
        // do stuff
    }
});

or by using a ButterKnife implentation (I have not tested yet since I dont have Android Studio available within my grasp at the moment, so if it works let me know) :

@Bind(R.id.my_button)
Button myButton;

@OnClick(R.id.button)
@Override
public void performClick(View view) {
     // do stuff
}

Regards,

Ricardo Vieira
  • 1,738
  • 1
  • 18
  • 26
  • could you use it with butterknive? :) – IARI Jul 22 '16 at 19:54
  • thank you - I haven't had a chance to try it, but I like the idea still. So far as i know the @Onclick decorator works on any Method with a proper signature - i am not sure how that will behave with an overriden method from the custom listener - i suppose butterknive creates its own ClickListener dynamically and that might jeopardise it. - I'll see if i can try it on monday. – IARI Jul 23 '16 at 11:03
  • 1
    Using our own listeners/constructs is always a nice way to guarantee a certain level o control. From recent ButterKnife, I've come to notice that there are several changes and improvements coming in the upcoming months regarding listeners and events. I myself like to mix both ButterKnife for simple and basic stuff, and custom constructs and implementations. Either way, im really curious about this case in particular since I myself have a few activities running with overlays (like my custom camera). If you come up to any elegant conclusion regarding this matter, please let me know. – Ricardo Vieira Jul 23 '16 at 12:26
0

We had seemingly similar bug in an Android app across the pages where button click would make a call to some service-side endpoint. Always thought why would Android in-built click binding would fail for such a common usecase. It turned out actually a very different thing in our case. And yes, it took our lot of time to finally grab the actual culprit.

It was Volley library's timeout value i.e. default 2.5 seconds with our slow server-side endpoints taking more than 10 seconds. Our buttons when clicked were calling some server endpoints. Say, booking appointment on click of a button. We saw duplicate appointments getting booked even on one time click too. Timeout would retry as per default retry count. By default, retry count is 1 so everytime our endpoints took more than 2.5 seconds, Volley would retry by reposting the same request and hence duplicating our actions. So this is issue with button click + Volley with default timeout+ endpoints taking more than timeout seconds. Solution was

  1. To adjust timeout of Volley by setting retry policy before we add request to queue. stringRequest.setRetryPolicy(new DefaultRetryPolicy( 10000,//non-default timeout in millseconds DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); requestQueue.add(stringRequest);

  2. To improve performance of our server-side endpoints to quick respond to clients.

Hope this helps people having sailed in the same boat or are sailing but clueless like we were :)

Pratap Singh
  • 401
  • 1
  • 4
  • 14