10

I have a Button with an OnClickListener. For illustrative purposes, consider a button that shows a modal dialog:

public class SomeActivity ... {

  protected void onCreate(Bundle state) {
    super.onCreate(state);

    findViewById(R.id.ok_button).setOnClickListener(
      new View.OnClickListener() {
        public void onClick(View v) {
          // This should block input
          new AlertDialog.Builder(SomeActivity.this)
            .setCancelable(true)
            .show();
      }
    });
}

Under normal usage, the alert dialog appears and blocks further input. Users must dismiss the dialog before they can tap the button again.

But sometimes the button's OnClickListener is called twice before the dialog appears. You can duplicate this fairly easily by tapping really fast on the button. I generally have to try several times before it happens, but sooner or later I'll trigger multiple onClick(...) calls before the dialog blocks input.

I see this behavior in Android 2.1 on the Motorola Droid phone. We've received 4 crash reports in the Market, indicating this occasionally happens to people.

Depending on what our OnClickListeners do, this causes all sorts of havoc. How can we guarantee that blocking dialogs actually block input after the first tap?

Eric Burke
  • 4,422
  • 3
  • 28
  • 29

2 Answers2

17

Romain Guy confirmed that this is indeed a bug in Android: "It happens only if the user manages to press the button twice in < 125ms. I believe we fixed that possible error in Froyo."

We'll use the "glass pane" pattern to work around the bug on older OSes. That is, we'll cover the screen with an invisible view. After the first click event, we'll make the view "visible" so it intercepts subsequent touch events.

It's not enough to prevent further events on just the one button. You need to block all subsequent events for the entire activity until the dialog is dismissed, the activity is resumed, etc., at which point you make the glass pane "invisible" again.

If that doesn't work, we'll just have to live with this and better tolerate unexpected extra events.

Bob Lee
  • 1,962
  • 1
  • 12
  • 9
  • Wow... are you *the* Bob Lee? – Pablo Fernandez May 23 '10 at 22:27
  • I guess that depends on who "the" Bob Lee is. :-) I don't know any other Bob Lee programmers though. :-) – Bob Lee May 24 '10 at 17:59
  • 1
    Bob and I are going with something very similar to what Bob describes above. The main difference is our "glass pane" isn't an actual view. Instead, all of our Activities extend a common base Activity, and all of our dialogs extend a base dialog. Since we have these base classes, we are able to introduce a boolean flag in each. This flag indicates if we are accepting or blocking input. In each of these base classes, we override dispatchTouchEvent. Based on the flag, we can simply return true, which intercepts and blocks the event. This approach seems to work. – Eric Burke May 25 '10 at 14:22
  • 6
    I disagree with Romain Guy that this is fixed in Froyo and above. This defect in Android is alive and well in ICS. – Cory Trese Jul 29 '12 at 18:17
  • Note that this only works around the bug if you're using something like a progress dialog to control the "glass pane". If you're dealing with the general case of button presses getting queued up, you have to do something like http://stackoverflow.com/questions/9730852/android-fast-button-presses-results-in-multiple-instances-of-intent or http://stackoverflow.com/questions/8077728/how-to-prevent-the-activity-from-loading-twice-on-pressing-the-button – Chris Marinos Sep 12 '12 at 07:52
  • I'm curious what technique you're using today. Are you still using the base class with touch intercept or have you found a simpler solution? In the past I have used the glass pane pattern to avoid bugs with event routing on older API 7- devices but I'm looking to replace it with a cleaner option. – James Wald Feb 13 '13 at 15:30
9

Thank you for trying, mdma, but this is a platform problem, not a problem with our code. Even worse, it's apparently not a problem that can be worked around in user code (it requires details from the touch screen driver that aren't passed along). Also, your code example doesn't do what you think it does. show() doesn't show the dialog immediately. It adds a message to the end of the event queue that eventually shows the dialog. More touch events could already be in the queue waiting to execute after onClick() returns.

I'm not sure why people are voting that answer up.

Bob Lee
  • 1,962
  • 1
  • 12
  • 9
  • Hi, I've deleted my answer. But I don't see how it's not possible to determine if you are in between states - between handling the click and showing the dialog, and then ignore further clicks. – mdma May 25 '10 at 14:46