114

I am trying to prevent the activity from loading twice if I press the button twice instantly after the first click.

I have an activity which loads on click of a button, say

 myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
       //Load another activity
    }
});

Now because the activity to be loaded has network calls, it takes a little time to load (MVC). I do show a loading view for this but if I press the button twice before that, I can see the activity being loaded twice.

Do any one know how to prevent this?

tejas
  • 2,435
  • 10
  • 37
  • 54
  • You can disable the button after opening the activity...and when activity finish,,re-enable it...u can detect the finish of second activity by calling onActivityResult function – Maneesh Nov 10 '11 at 10:02
  • Disable the button when it is first clicked and re-enable it later only when you want the button to be clicked again. – JimmyB Nov 10 '11 at 10:03
  • disabling doesn't work in a simple manner if the very next statement is for some long process or activity start... To disable button you must have to create a separate thread... – Awais Tariq Nov 10 '11 at 10:05
  • If hitting the same API twice refer here: https://techstricks.com/avoid-multiple-requests-when-using-volley/ – Shailendra Madda Feb 15 '18 at 06:42
  • Possible duplicate of [Avoid button multiple rapid clicks](https://stackoverflow.com/questions/16534369/avoid-button-multiple-rapid-clicks) – Arnab Kar Dec 11 '18 at 14:17

21 Answers21

179

Add this to your Activity definition in AndroidManifest.xml...

android:launchMode = "singleTop"

For example:

<activity
            android:name=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            android:launchMode = "singleTop"/>
Kaiser
  • 606
  • 8
  • 22
Awais Tariq
  • 7,724
  • 5
  • 31
  • 54
  • ok i guess you are doing some long processing after starting new activity.. That's why the screen turns out black. Now if you want to avoid that black screen, you should show a progress dialog at the start of activity and do the long processing in a separate thread (i.e. UI Thread or Simply use async class). Once your processing is done hide that dialog. It is the best solution in my knowledge and I have used it several times...:) – Awais Tariq Nov 10 '11 at 12:51
  • I have the dialog to show. But yeah I have one method pointing web in the onCreate. But is this is the only solution? Because at this point, I want to handle without changing thread and all. So do you know other possible ways. And I'm having the button in my list adapter and I have declared the method for it in the xml, not programmatically – tejas Nov 10 '11 at 13:00
  • 2
    what else is possible??? One way or other you must have to implement threading to get a smooth looking app... Try it dude..;) Just put all the current code in a method and call that method from a separate thread at same place where you have written it earlier... It'll hardly increase five to six lines of code.. – Awais Tariq Nov 10 '11 at 17:38
  • I use a webview with a javascript interface, in Jelly bean my method is fired twice!, this solution works perfect! thanks Awais – Jorgesys Sep 25 '12 at 01:44
  • Thanks for you solution. But when startActivity(...), I see a black screen for a while. I try to use singleTask instead of singleInstance and it works fine. – VAdaihiep Apr 25 '14 at 12:47
  • 23
    This prevents two instances of the activity from existing, but it does not prevent the code from running twice, incorrectly. The accepted answer is better, despite having fewer up votes. – lilbyrdie May 20 '14 at 15:19
  • 19
    This is wrong, it makes the activity never exist twice, even in different tasks. The correct way would be `android:launchMode = "singleTop"`, which achieves the effect without breaking Android multitasking. The documentation states that most apps should not use the `singleInstance` option. – Nohus May 16 '17 at 18:06
  • FYI: If you use activity transitions stuff might break if you rely on this setting – Diolor Mar 05 '19 at 22:58
79

In the button's event listener, disable the button and show another activity.

    Button b = (Button) view;
    b.setEnabled(false);

    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);

Override onResume() to re-enable the button.

@Override
    protected void onResume() {
        super.onResume();

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setEnabled(true);
    }
wannik
  • 12,212
  • 11
  • 46
  • 58
  • 1
    This is the correct approach. It will even handle Button Selected States for you (if you provide them) and all the Material Design “goodies” you’d expect from a simple standard Widget. I can’t believe people use timers for this. Then you start seeing strange libraries to handle things like these… – Martin Marconcini Mar 05 '18 at 22:06
42

You can use the intent flags like this.

Intent intent = new Intent(Class.class);    
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(intent);

It will make only one activity be open at the top of the history stack.

Darush
  • 11,403
  • 9
  • 62
  • 60
Carlos EduardoL
  • 670
  • 6
  • 14
  • 4
    This answer, combined with the most upvoted answer seems to work best. Use this flag in the Activity's manifest: `android:launchMode = "singleTop"`, this way it's solved without having to add the flag to every Intent. – Nohus May 16 '17 at 18:09
  • 1
    this is not useful when you need nested activities, because you can not have two same type activity. – Bheid Jun 25 '19 at 10:51
  • 9
    This is not working in the case of startActivityForResult – raj Jul 16 '19 at 10:52
35

Since SO doesn't allow me to comment on other answers, I have to pollute this thread with a new answer.

Common answers for the "activity opens twice" problem and my experiences with these solutions (Android 7.1.1):

  1. Disable button that starts the activity: Works but feels a little clumsy. If you have multiple ways to start the activity in your app (e.g. a button in the action bar AND by clicking on an item in a list view), you have to keep track of the enabled/disabled state of multiple GUI elements. Plus it's not very convenient to disable clicked items in a list view, for example. So, not a very universal approach.
  2. launchMode="singleInstance": Not working with startActivityForResult(), breaks back navigation with startActivity(), not recommended for regular applications by Android manifest documentation.
  3. launchMode="singleTask": Not working with startActivityForResult(), not recommended for regular applications by Android manifest documentation.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: Breaks back button.
  5. FLAG_ACTIVITY_SINGLE_TOP: Not working, activity is still opened twice.
  6. FLAG_ACTIVITY_CLEAR_TOP: This is the only one working for me.

EDIT: This was for starting activities with startActivity(). When using startActivityForResult() I need to set both FLAG_ACTIVITY_SINGLE_TOP and FLAG_ACTIVITY_CLEAR_TOP.

Andy Roid
  • 393
  • 3
  • 6
  • FLAG_ACTIVITY_CLEAR_TOP: This is the only one working for me on Android 7.1.1 – Mingjiang Shi Oct 19 '18 at 04:11
  • 2
    I am using "FLAG_ACTIVITY_REORDER_TO_FRONT" and it is working just fine and Back button also acts normal. What exactly did you mean by "Breaks back button"? Could you clarify on that? – amira Jan 04 '19 at 07:40
  • 1
    I found that "REORDER" flag had a bug... and it was not **reordering** in KitKat. However, I checked it in Lollipop and Pie, it is working fine. – amira Jan 04 '19 at 09:08
11

It was only working for me when startActivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Shailendra Madda
  • 20,649
  • 15
  • 100
  • 138
7

Use singleInstance to avoid activity to invoke twice.

<activity
            android:name=".MainActivity"
            android:label="@string/activity"
            android:launchMode = "singleInstance" />
4

Let's say @wannik is right but if we have more than 1 button calling same action listener and i click two buttons once almost same time before starting next activity...

So it is good if you have field private boolean mIsClicked = false; and in the listener:

if(!mIsClicked)
{
    mIsClicked = true;
    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);
}

And onResume() we need to return the state:

@Override
protected void onResume() {
    super.onResume();

    mIsClicked = false;
}

What's the deifference between my and @wannik's answer?

If you set enabled to false in the listener of it's calling view other button using same listener will still be enabled. So to be sure that listener's action is not called twice you need to have something global that disables all callings of the listener(nevermind if it's new instance or no)

What is the difference between my answer and others?

They are thinking in right way but they are not thinking for future return to the same instance of the calling activity :)

  • servoper, thank you for your research. This question has been already been solved, how ever your answer also looks promising for the situation you told. Let me try and come with the result :) – tejas Jun 24 '13 at 08:02
  • 1
    I have this issue in one of my games. I have "select level" baloons that have same listener and the views are just different by tags. So if I fast choose two baloons it starts two activities. I know that because the new activity starts sound.. and in this case sound is played twice... but you can check it by clicking back which will drive you to previous activity – Sir NIkolay Cesar The First Jun 27 '13 at 12:52
  • 1
    This isn't sufficient. You need to use a `synchronized(mIsClicked) {...}` as well to be 100% safe. – Monstieur Dec 20 '13 at 12:59
  • 1
    @Monstieur you don’t need a synchronized block because this is all Main Thread… – Martin Marconcini Mar 05 '18 at 21:41
  • @MartinMarconcini Just because it happens to be safe in an Android activity doesn't make it good code. If it was a standalone class it would have to be documented as not thread safe. – Monstieur Mar 07 '18 at 06:11
  • @Monstieur There is a reason Android UI (and iOS UIKit) code is Main Thread only, precisely so you don’t have to worry about making it thread safe, it’s Main Thread Safe by design. That’s how things are done on Android (and iOS). You only need to worry about threading when you are actually using threading, which the OP is not. Adding a synchronized block in that call would be a complete waste of byte code and ultimately CPU. You can’t click a “button” from a background thread, there is one and only one Looper for that, it’s the Main thread, so no, you don’t need to make it “good code”. – Martin Marconcini Mar 07 '18 at 17:32
  • @MartinMarconcini It's unclear what the OP is doing, and he's already mentioned it's running some web service call that seems to be asynchronous since it allows the button to be clicked twice. Who knows where he's going to check the variable from? I wouldn't synchronize it in my code, since `private` means I am fully responsible for it, but I wouldn't blindly tell someone to do the same, especially if the question is about something this basic. – Monstieur Mar 08 '18 at 18:10
4

For this situation, I'll go for one of two approached, singleTask in manifest.xml OR a flag in the Activity's onResume() & onDestroy() methods respectively.

For the first solution: I prefer to use singleTask for the activity in the manifest rather than singleInstance, as per using singleInstance I figured out that in some occasions the activity creating a new separate instance for itself which result to have a two separate applications window in the running apps in bcakground and besides extra memory allocations that would result a very bad User Experience when the user opens the apps view to choose some app to resume. So, the better way is to have the activity defined at the manifest.xml like the following:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"</activity>

you can check activity launch modes here.


For the second solution, you have to just define a static variable or a preference variable, for example:

public class MainActivity extends Activity{
    public static boolean isRunning = false;

    @Override
    public void onResume() {
        super.onResume();
        // now the activity is running
        isRunning = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // now the activity will be available again
        isRunning = false;
    }

}

and from the other side when you want to launch this activity, just check:

private void launchMainActivity(){
    if(MainActivity.isRunning)
        return;
    Intent intent = new Intent(ThisActivity.this, MainActivity.class);
    startActivity(intent);
}
Muhammed Refaat
  • 8,914
  • 14
  • 83
  • 118
3

Hope this helps:

 protected static final int DELAY_TIME = 100;

// to prevent double click issue, disable button after click and enable it after 100ms
protected Handler mClickHandler = new Handler() {

    public void handleMessage(Message msg) {

        findViewById(msg.what).setClickable(true);
        super.handleMessage(msg);
    }
};

@Override
public void onClick(View v) {
    int id = v.getId();
    v.setClickable(false);
    mClickHandler.sendEmptyMessageDelayed(id, DELAY_TIME);
    // startActivity()
}`
thanhbinh84
  • 17,876
  • 6
  • 62
  • 69
3

// variable to track event time

private long mLastClickTime = 0;

2.In onClick check that if current time and last click time difference are less than i second then dont't do anything(return) else go for click event

 @Override
public void onClick(View v) {
    // Preventing multiple clicks, using threshold of 1 second
    if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
        return;
          }
    mLastClickTime = SystemClock.elapsedRealtime();
            // Handle button clicks
            if (v == R.id.imageView2) {
        // Do ur stuff.
         }
            else if (v == R.id.imageView2) {
        // Do ur stuff.
         }
      }
 }
44kksharma
  • 2,740
  • 27
  • 32
3

I think you're going about solving the problem the wrong way. Generally it is a bad idea for an activity to be making long-running web requests in any of its startup lifecycle methods (onCreate(), onResume(), etc). Really these methods should simply be used to instantiate and initialise objects your activity will use and should therefore be relatively quick.

If you need to be performing a web request then do this in a background thread from your newly launched activity (and show the loading dialog in the new activity). Once the background request thread completes it can update the activity and hide the dialog.

This then means your new activity should get launched immediately and prevent the double click from being possible.

tomato
  • 3,373
  • 1
  • 25
  • 33
2

Other very very simple solution if you no want use onActivityResult() is disable the button for 2 seconds (or time you want), is not ideal, but can solve partly the problem is some cases and the code is simple:

   final Button btn = ...
   btn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            //start activity here...
            btn.setEnabled(false);   //disable button

            //post a message to run in UI Thread after a delay in milliseconds
            btn.postDelayed(new Runnable() {
                public void run() {
                    btn.setEnabled(true);    //enable button again
                }
            },1000);    //1 second in this case...
        }
    });
Gilian
  • 422
  • 7
  • 12
2

add Launch Mode as single task in manifest to avoid activity opening twice on clicking

<activity
        android:name=".MainActivity"
        android:label="@string/activity"
        android:launchMode = "singleTask" />
1

Just maintain one flag in button onClick method as:

public boolean oneTimeLoadActivity = false;

    myButton.setOnClickListener(new View.OnClickListener() {
          public void onClick(View view) {
               if(!oneTimeLoadActivity){
                    //start your new activity.
                   oneTimeLoadActivity = true;
                    }
        }
    });
Balaji Khadake
  • 3,449
  • 4
  • 24
  • 38
0

If you're using onActivityResult, you could use a variable to save state.

private Boolean activityOpenInProgress = false;

myButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View view) {
    if( activityOpenInProgress )
      return;

    activityOpenInProgress = true;
   //Load another activity with startActivityForResult with required request code
  }
});

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if( requestCode == thatYouSentToOpenActivity ){
    activityOpenInProgress = false;
  }
}

Works on back button pressed too because request code is returned on event.

Umang
  • 211
  • 3
  • 12
0

We can use button debounce as well.

public abstract class DebounceOnClickListener implements View.OnClickListener {

    private final long minimumInterval;
    private final Map<View, Long> lastClickMap;

   
    public abstract void debounceOnClick(View view);

  
    public DebounceOnClickListener(long minIntervalMilliSec) {
        this.minimumInterval = minIntervalMilliSec;
        this.lastClickMap = new WeakHashMap<View, Long>();
    }

    @Override
    public void onClick(View clickedView) {
        Long previousClickTimestamp = lastClickMap.get(clickedView);
        long currentTimestamp = SystemClock.uptimeMillis();

        lastClickMap.put(clickedView, currentTimestamp);
        if (previousClickTimestamp == null ||
                (currentTimestamp - previousClickTimestamp.longValue() > minimumInterval)) {
            debounceOnClick(clickedView);
        }
    }
}
0

In kotlin you can use this extension function to disable a view's click , this will enable the view after the time

fun View.disableClick(time: Long = 1000) {
    isEnabled = false
    postDelayed({
        isEnabled = true
    }, time)
}   

you can call the function like below

  binding.btGetStarted.setOnClickListener {
     //disabling button click
      binding.btGetStarted.disableClick()
     //activity redirection
     ActivityUtils.startActivity(this,RegisterActivity::class.java, isFinish = false)
        }
-1

You could just override startActivityForResult and use instance variable:

boolean couldStartActivity = false;

@Override
protected void onResume() {
    super.onResume();

    couldStartActivity = true;
}

@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if (couldStartActivity) {
        couldStartActivity = false;
        intent.putExtra(RequestCodeKey, requestCode);
        super.startActivityForResult(intent, requestCode, options);
    }
}
Aleksei Minaev
  • 1,488
  • 16
  • 17
-1
myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      myButton.setOnClickListener(null);
    }
});
Thunder Rabbit
  • 5,405
  • 8
  • 44
  • 82
-1

Use a flag variable set it to true, Check if its true just return else perform Activity Call.

You can also use setClickable(false) one executing the Activity Call

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 
MKJParekh
  • 34,073
  • 11
  • 87
  • 98
-4

You can try this also

Button game = (Button) findViewById(R.id.games);
        game.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                Intent myIntent = new Intent(view.getContext(), Games.class);
                startActivityForResult(myIntent, 0);
            }

        });
Karthik
  • 4,943
  • 19
  • 53
  • 86