Clicking the back button twice to exit an activity
Asked Answered
M

48

389

I've noticed this pattern in a lot of Android apps and games recently: when clicking the back button to "exit" the application, a Toast comes up with a message similar to "Please click BACK again to exit".

I was wondering, as I'm seeing it more and more often, is that a built-in feature that you can somehow access in an activity? I've looked at the source code of many classes but I can't seem to find anything about that.

Of course, I can think about a few ways to achieve the same functionality quite easily (the easiest is probably to keep a boolean in the activity that indicates whether the user already clicked once...) but I was wondering if there's something already here.

EDIT: As @LAS_VEGAS mentioned, I didn't really mean "exit" in the traditional meaning. (i.e. terminated) I meant "going back to whatever was open before the application start activity was launched", if that makes sense :)

Mangrum answered 8/12, 2011 at 12:8 Comment(4)
[Android - Confirm app exit with toast] [1]: #14006961Ankeny
I had the same problem when using the HoloEverywhere Library, too simply you can add android:launchMode="singleTask" to you activity definition in the manifest file.Directorate
Other solution #8431305Palmitate
Possible duplicate of clicking on the android back button twice to exit the appNarwhal
S
1067

In Java Activity:

boolean doubleBackToExitPressedOnce = false;

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }
        
    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();
        
    new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
        
        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 

In Kotlin Activity:

private var doubleBackToExitPressedOnce = false
override fun onBackPressed() {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed()
            return
        }

        this.doubleBackToExitPressedOnce = true
        Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show()

        Handler(Looper.getMainLooper()).postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000)
    }

I Think this handler helps to reset the variable after 2 second.

Si answered 27/11, 2012 at 6:32 Comment(11)
Best answer! You may also add condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) { in case of Fragment-based addGamache
I agree, this is definitely the best answer and should be the accepted answer.Cerebral
You should remove the Runnable when exit application.Burletta
Check my answer.I have modified the answer given by Sudheesh B Nair to cover the above suggested comments.Muncey
It's a nice quick solution/answer but I don't agree that it's the best solution. And for the ones who think this will be best answer again I can't agree. These solution causes leaks and will require extra effort for handling. Check the aswers below for further details.Violaceous
@Zefnus, Why ? Please explain for the beginners like me.Prognathous
@Prognathous you shouldn't just register a callback to a handler and forget all about the rest. It will cause a leak when you don't need the handler anymore. You should rather remove the callback and manage handler lifecycle in order to prevent leaks. An easier and more optimized solution without handler usage is described below. Please check my answer and feel free to ask any questions.Violaceous
if i add this code block me getting error in handlerFiscus
Handler must be handled with care now. If user presses back, it might leak.Leija
I personally like using a Snackbar instead of a Toast. The Toast persists on the screen for some extra time after the app is closed despite using LENGTH_SHORTAugite
This is not good solution. Get the System currentMillisecons everytime on each back press and check the difference with previous System currentMillisecons fetched. You can also set the milliseconds to allow next backpress for finishing activity. This is very robust approach. Or else, you can also use Snackbar to prompt user to exit (but that's not recommended as per UX guidelines). In order to prompt for exit, use AlertDialog specifically.Howze
T
255

Sudheesh B Nair's has a nice (and accepted) answer on the question, which i think should have a better alternative such as;

What's wrong with measuring time passed and checking if TIME_INTERVAL miliseconds (say 2000) passed since the last back press. The following sample code uses System.currentTimeMillis(); to store the time onBackPressed() is called;

private static final int TIME_INTERVAL = 2000; // # milliseconds, desired time passed between two back presses.
private long mBackPressed;

@Override
public void onBackPressed()
{
    if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) 
    { 
        super.onBackPressed(); 
        return;
    }
    else { Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show(); }

    mBackPressed = System.currentTimeMillis();
}

Back on accepted answer critique; Using a flag to indicate if it was pressed in last TIME_INTERVAL (say 2000) milliseconds and set - reset is via Handler's postDelayed() method was the first thing to come in my mind. But the postDelayed() action should be cancelled when activity is closing, removing the Runnable.

In order to remove the Runnable, it must not be declared anonymous, and be declared as member along with the Handler aswell. Then removeCallbacks() method of Handler can be called appropriately.

The following sample is the demonstration;

private boolean doubleBackToExitPressedOnce;
private Handler mHandler = new Handler();

private final Runnable mRunnable = new Runnable() {
    @Override
    public void run() {
        doubleBackToExitPressedOnce = false;                       
    }
};

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

    if (mHandler != null) { mHandler.removeCallbacks(mRunnable); }
}

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

    mHandler.postDelayed(mRunnable, 2000);
}

Thanks to @NSouth for contributing; In order to prevent toast message appearing even after the application is closed, Toast can be declared as a member - say mExitToast - and can be cancelled via mExitToast.cancel(); just before super.onBackPressed(); call.

Tsang answered 11/3, 2014 at 17:34 Comment(18)
For the ones who think that it's the same what Sudheesh B Nair said: Same functionality, better performance. so +1.Condon
I like this answer and I think it's the best. I mean i don't THINK it is, IT IS the best answer, for the reasons stated above. I hope you get more upvotes for this one. One comment though: no one finds it a it odd that the toast persists a couple of seconds after the app closes? No one cares to cancel the toast? I know it may be a small detail but I think that should happen. What do you guys think?Meaning
@Meaning Thank you. You are right, it looks odd when toast continues to appear after application is terminated. Maybe most of the users don't care because it happens. In Android, toast messages appear in launcher and it is usual.Violaceous
@joonty thanks for edit, that int keyword has been missing for some time. It will compile now (:Violaceous
This crashed for me because mHandler was never assigned. I declared it with private Handler mHandler = new Handler() and it works fine now. I'm somewhat new to Handlers, so I hope that's a correct implementation.Aggravate
@Aggravate Second code block was a sample using mHandlers in order to show it required more effort. I suggest you consider using the first code block, which doesn't use a handler.Violaceous
@Aggravate Thank you for your attempt to add value to the answer with your rejected edit. I have reviewed the content you added, and included it in my answer.Violaceous
@Meaning I suppose we have a solid solution for toast message persisting after the application closes now.Violaceous
@Zefnus : Don't you think that my answer is almost same as yours? I couldn't find any significant reason to add new one.Muncey
@MehulJoisar Did you read the entire answer? I wanted to make clear that the solution i suggest is the first code block, without handler. Solution without handler is not same as your solution in anyway as it uses handler - which is not a short nor simple solution for the issue. Your answer doesn't explain the pitfalls of handler and why you remove them aswell.Violaceous
@Zefnus Thanks for the amazing answer. If I want to click the back button 3 times or 5 times to exit the app. How can I implement it? (Just curious. I know the user will have bad experience)Deucalion
Now that's the kind of answer I really appreciate. Very well explained and works efficiently! +1 for the extra thought!Faggot
Shouldn't mBackPressed be initialized?Trinh
Since this answer is arguably better than the accepted answer, I'd like to echo Anton's comment on the accepted answer: "You may also add condition if (fragmentManager.getBackStackEntryCount() != 0) { in case of Fragment-based add"Fixture
This is a best answerPalmitate
@SrikarReddy - recipe for changing any code from boolean flag to a counter: Add const int REQUIRED_COUNT = 3; Replace boolean pressedOnce with int count = 0. Replace pressedOnce = false with count = 0. Replace pressedOnce = true with pressedOnce += 1. Replace if (pressedOnce) with if (counter >= REQUIRED_COUNT).Outman
the first block is simple and best solutionIncombustible
this answer is the best +1Throwback
M
31

Just thought I would share how I did it in the end, I just added in my activity:

private boolean doubleBackToExitPressedOnce = false;

@Override
protected void onResume() {
    super.onResume();
    // .... other stuff in my onResume ....
    this.doubleBackToExitPressedOnce = false;
}

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }
    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, R.string.exit_press_back_twice_message, Toast.LENGTH_SHORT).show();
}

And it just works exactly as I want. Including the reset of the state whenever the activity is resumed.

Mangrum answered 8/12, 2011 at 23:19 Comment(4)
With this solution the two back presses can have an arbitrary amount of time between them. So you can press Back once and then press Back again a minute later and the application will exit. This is not the behaviour that the user will expect.Cerebral
I think it's a matter of taste. In this case you notify the user just once, so the user knows he is in the main activity and another back press will exit the app (problably after the user has pressed back a couple of times to go back to the main screen). If, later, the user presses back again, we can suppose he wants to exit the app (unless he has navigated into another activities and problably lost track of how deep he got). In the accepted answer above, you consider that the user might forget that he is already in the main screen. Both are fine, depending on what you want or consider.Lunalunacy
@FerranMaylinch - I disagree. This is not merely a matter of taste. If significant time has passed, we should assume that the user has done other actions in the meantime, and no longer considers what he did previously and chose not to continue with to apply. Indeed, all but the most exceptional user will not even still have it in his mind that he previously did that. Without a time limit, you have left the app in an invisible mode that the user has no way of knowing about. I absolutely consider that to be poor user interface design. Users will be surprised.Outman
IMHO, better variants on this here and here.Outman
S
28

Process Flow Diagram: Press again to exit.

Java Code:

private long lastPressedTime;
private static final int PERIOD = 2000;

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        switch (event.getAction()) {
        case KeyEvent.ACTION_DOWN:
            if (event.getDownTime() - lastPressedTime < PERIOD) {
                finish();
            } else {
                Toast.makeText(getApplicationContext(), "Press again to exit.",
                        Toast.LENGTH_SHORT).show();
                lastPressedTime = event.getEventTime();
            }
            return true;
        }
    }
    return false;
}
Susi answered 29/12, 2012 at 7:31 Comment(1)
I initally voted this as being helpful. Trouble is, for some reason, this doesn't work on some handsets. The onBackPressed method works best, but then you don't have the timestamps, so you need the handlers as the accepted answer states.Thornberry
L
28

There is very simplest way among all these answers.

Simply write following code inside onBackPressed() method.

long back_pressed;

@Override
public void onBackPressed() {
    if (back_pressed + 1000 > System.currentTimeMillis()){
        super.onBackPressed();
    }
    else{
        Toast.makeText(getBaseContext(),
                "Press once again to exit!", Toast.LENGTH_SHORT)
                .show();
    }
    back_pressed = System.currentTimeMillis();
}

You need to define back_pressed object as long in activity.

Leija answered 24/5, 2014 at 6:39 Comment(0)
P
18

You may use a snackbar instead of a toast, so you can rely on their visibility to decide whether to close the app or not. Take this example:

Snackbar mSnackbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final LinearLayout layout = findViewById(R.id.layout_main);
    mSnackbar = Snackbar.make(layout, R.string.press_back_again, Snackbar.LENGTH_SHORT);
}

@Override
public void onBackPressed() {
    if (mSnackbar.isShown()) {
        super.onBackPressed();
    } else {
        mSnackbar.show();
    }
}
Presley answered 7/1, 2017 at 14:8 Comment(3)
Thanks for this solution. Simple, effective, no risk.Schema
Out of box solution. (clap)Subadar
Simple and Great!! But this affect on all navigation back button!Oraleeoralia
M
13

Based upon the correct answer and suggestions in comments, I have created a demo which works absolutely fine and removes the handler callbacks after being used.

MainActivity.java

package com.mehuljoisar.d_pressbacktwicetoexit;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.widget.Toast;

public class MainActivity extends Activity {

    private static final long delay = 2000L;
    private boolean mRecentlyBackPressed = false;
    private Handler mExitHandler = new Handler();
    private Runnable mExitRunnable = new Runnable() {

        @Override
        public void run() {
            mRecentlyBackPressed=false;   
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onBackPressed() {

        //You may also add condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) // in case of Fragment-based add
        if (mRecentlyBackPressed) {
            mExitHandler.removeCallbacks(mExitRunnable);
            mExitHandler = null;
            super.onBackPressed();
        }
        else
        {
            mRecentlyBackPressed = true;
            Toast.makeText(this, "press again to exit", Toast.LENGTH_SHORT).show();
            mExitHandler.postDelayed(mExitRunnable, delay);
        }
    }

}

I hope it will be helpful !!

Muncey answered 3/2, 2014 at 7:43 Comment(2)
Are you certain removing handler in onBackPressed() fix the memory leak issue?Violaceous
@Zefnus : It will fix as far as I know. Correct me if I am wrong. How did you trace memory issue with above code?Muncey
C
11
 public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

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

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;
        }
    }, 2000);

Declare Variableprivate boolean doubleBackToExitPressedOnce = false;

Paste this in your Main Activity and this will solve your issue

Culm answered 13/5, 2015 at 10:18 Comment(0)
G
10

It is not a good idea to use a Runnable when exit the application, I recently figure out a much simpler way to record and compare the period between two BACK button clicks. Sample code as following:

private static long back_pressed_time;
private static long PERIOD = 2000;

@Override
public void onBackPressed()
{
        if (back_pressed_time + PERIOD > System.currentTimeMillis()) super.onBackPressed();
        else Toast.makeText(getBaseContext(), "Press once again to exit!", Toast.LENGTH_SHORT).show();
        back_pressed_time = System.currentTimeMillis();
}

This will do the trick to exit the application by a double BACK button clicks within a certain delay period which is 2000 millisecond in sample.

Guillaume answered 21/1, 2015 at 20:33 Comment(0)
S
8

The Accepted answer is Best one but if you are using Android Design Support Library then you can use SnackBar for Better Views.

   boolean doubleBackToExitPressedOnce = false;

    @Override
    public void onBackPressed() {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed();
            return;
        }

        this.doubleBackToExitPressedOnce = true;

        Snackbar.make(findViewById(R.id.photo_album_parent_view), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show();

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

            @Override
            public void run() {
                doubleBackToExitPressedOnce=false;
            }
        }, 2000);
    }
Similarity answered 14/12, 2015 at 12:52 Comment(0)
B
7

It's not a built in functionality. I think it is not even the recommended behavior. Android apps are not meant to exit:

Why dont Android applications provide an "Exit" option?

Buy answered 8/12, 2011 at 12:11 Comment(2)
Point taken. By exit, I meant "going back to home screen"Mangrum
Still it is not a built in functionality. However I'm unaware of any guideline against this. As an android user, I like such functionality.Buy
L
6

Zefnus's answer using System.currentTimeMillis() is the best one (+1). The way I did it is not better than that, but still posting it to add to the above ideas.

If the toast is not visible when the back button is pressed, the toast is displayed, whereas, if it is visible (back has already been pressed once within the last Toast.LENGTH_SHORT time), then it exits.

exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT);
.
.
@Override
public void onBackPressed() {
   if (exitToast.getView().getWindowToken() == null) //if toast is currently not visible
      exitToast.show();  //then show toast saying 'press againt to exit'
   else {                                            //if toast is visible then
      finish();                                      //or super.onBackPressed();
      exitToast.cancel();
   }
}
Loathly answered 17/4, 2014 at 6:39 Comment(2)
Is a good way to do this, but if you have more toast messages is not.Dung
@BoldijarPaul No, this code only checks the status of that specific toast. So any other toasts won't affect the behaviour.Verret
V
6

Recently, I needed to implement this back button feature in an app of mine. The answers on the original question were useful, but I had to take two more points into consideration:

  1. At some points in time, the back button is disabled
  2. The main activity is using fragments in combination with a back stack

Based on the answers and comments, I created the following code:

private static final long BACK_PRESS_DELAY = 1000;

private boolean mBackPressCancelled = false;
private long mBackPressTimestamp;
private Toast mBackPressToast;

@Override
public void onBackPressed() {
    // Do nothing if the back button is disabled.
    if (!mBackPressCancelled) {
        // Pop fragment if the back stack is not empty.
        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            super.onBackPressed();
        } else {
            if (mBackPressToast != null) {
                mBackPressToast.cancel();
            }

            long currentTimestamp = System.currentTimeMillis();

            if (currentTimestamp < mBackPressTimestamp + BACK_PRESS_DELAY) {
                super.onBackPressed();
            } else {
                mBackPressTimestamp = currentTimestamp;

                mBackPressToast = Toast.makeText(this, getString(R.string.warning_exit), Toast.LENGTH_SHORT);
                mBackPressToast.show();
            }
        }
    }
}

The code above assumes that the support library is used. If you use fragments but not the support library, you want to replace getSupportFragmentManager() by getFragmentManager().

Remove the first if, if the back button is never cancelled. Remove the second if, if you don`t use fragments or a fragment back stack

Also, it is important to be aware that the method onBackPressed is supported since Android 2.0. Check this page for an elaborate description. To make the back press feature work on older versions as well, add the following method to your activity:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)  {
    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR
            && keyCode == KeyEvent.KEYCODE_BACK
            && event.getRepeatCount() == 0) {
        // Take care of calling this method on earlier versions of
        // the platform where it doesn't exist.
        onBackPressed();
    }

    return super.onKeyDown(keyCode, event);
}
Vocative answered 18/11, 2014 at 11:14 Comment(0)
J
6
  1. Declare a global Toast variable for MainActivity Class. example: Toast exitToast;
  2. Initialize it in onCreate view method. example: exitToast = Toast.makeText(getApplicationContext(), "Press back again to exit", Toast.LENGTH_SHORT);
  3. Finally create a onBackPressedMethod as Follows:

    @Override
    public void onBackPressed() {
    
        if (exitToast.getView().isShown()) {
            exitToast.cancel();
            finish();
        } else {
            exitToast.show();
        }
    }
    

This works correctly, i have tested. and I think this is much simpler.

Javanese answered 11/4, 2016 at 14:43 Comment(0)
E
6

In java

private Boolean exit = false; 

if (exit) {
onBackPressed(); 
}

 @Override
public void onBackPressed() {
    if (exit) {
        finish(); // finish activity
    } else {
        Toast.makeText(this, "Press Back again to Exit.",
                Toast.LENGTH_SHORT).show();
        exit = true;
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                exit = false;
            }
        }, 3 * 1000);

    }
}

In kotlin

 private var exit = false

 if (exit) {
        onBackPressed()
         }

 override fun onBackPressed(){
           if (exit){
               finish() // finish activity
           }else{
            Toast.makeText(this, "Press Back again to Exit.",
                    Toast.LENGTH_SHORT).show()
            exit = true
            Handler().postDelayed({ exit = false }, 3 * 1000)

        }
    }
Essene answered 7/12, 2017 at 11:11 Comment(0)
D
5

I know this is a very old question, but this is the easiest way to do what you want.

@Override
public void onBackPressed() {
   ++k; //initialise k when you first start your activity.
   if(k==1){
      //do whatever you want to do on first click for example:
      Toast.makeText(this, "Press back one more time to exit", Toast.LENGTH_LONG).show();
   }else{
      //do whatever you want to do on the click after the first for example:
      finish(); 
   }
}

I know this isn't the best method, but it works fine!

Discotheque answered 19/4, 2013 at 6:17 Comment(2)
This is not the general behaviour of "clicking back button twice to exit". Like BruceHill's comment on accepted answer points out, your answer doesn't handle time issue tooTiruchirapalli
but with this you can click back, it will show the message, and then you can wait a little longer, go back again and it will close the app, and it will not handle the behaviour of double back timingMcnew
R
4

I would normally add a comment, but my reputation doesn't allow this. So here's my two cents:

In Kotlin you can use coroutines to delay the setting to false:

private var doubleBackPressed = false
private var toast : Toast ?= null

override fun onCreate(savedInstanceState: Bundle?) {
    toast = Toast.maketext(this, "Press back again to exit", Toast.LENGTH_SHORT)
}

override fun onBackPressed() {
    if (doubleBackPressed) {
        toast?.cancel()
        super.onBackPressed()
        return
    }
    this.doubleBackPressed = true
    toast?.show()
    GlobalScope.launch {
        delay(2000)
        doubleBackPressed = false
    }
}

You will have to import:

import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
Ratiocinate answered 19/9, 2018 at 9:35 Comment(1)
This is a nice approach with the use of coroutines, but as at 2022, super.onBackPressed has been deprecated. Hence this answer requires modification. Call finishActivity() to exit instead.Bastardize
W
3

For this purpose I have implemented the following function:

private long onRecentBackPressedTime;
@Override
public void onBackPressed() {
    if (System.currentTimeMillis() - onRecentBackPressedTime > 2000) {
       onRecentBackPressedTime = System.currentTimeMillis();
       Toast.makeText(this, "Please press BACK again to exit", Toast.LENGTH_SHORT).show();
       return;
     }
   super.onBackPressed();
}
Waist answered 27/5, 2016 at 7:5 Comment(0)
P
3

A slightly better method than Zefnus I think. Call System.currentTimeMillis() just one time and omit return; :

long previousTime;

@Override
public void onBackPressed()
{
    if (2000 + previousTime > (previousTime = System.currentTimeMillis())) 
    { 
        super.onBackPressed();
    } else {
        Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show();
    }
}
Ping answered 22/6, 2016 at 13:31 Comment(0)
P
3

This also helps when you have previous stack activity stored in stack.

I have modified Sudheesh's answer

boolean doubleBackToExitPressedOnce = false;

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        //super.onBackPressed();

  Intent intent = new Intent(Intent.ACTION_MAIN);
                    intent.addCategory(Intent.CATEGORY_HOME);
                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//***Change Here***
                    startActivity(intent);
                    finish();
                    System.exit(0);
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

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

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 
Papoose answered 11/8, 2016 at 12:12 Comment(0)
C
3

I think this is what you need, I mean when we want to show this toast, when there is only one activity in the stack and user pressing back from this last activity of the stack.

var exitOpened=false // Declare it globaly

and in onBackPressed method change it like:

override fun onBackPressed() {
        if (isTaskRoot && !exitOpened)
        {
            exitOpened=true
            toast("Please press back again to exit")
            return
        }
        super.onBackPressed()
    }

Here, isTaskRoot will return true if the current activity is the root activity(first activity) of the stack and false if it is not.

You can check the official documentation here

Carbajal answered 3/6, 2019 at 6:53 Comment(0)
B
2
@Override public void onBackPressed() {
   Log.d("CDA", "onBackPressed Called");
   Intent intent = new Intent();
   intent.setAction(Intent.ACTION_MAIN);
   intent.addCategory(Intent.CATEGORY_HOME);

   startActivity(intent);
}
Butyrin answered 25/3, 2014 at 6:2 Comment(1)
How does this even handle the double press scenario? As soon as I hit back, this launches an activity.Ursala
E
2

Here is the full working code. And also don't forget to remove the callbacks so that it don't cause a memory leak in the app. :)

private boolean backPressedOnce = false;
private Handler statusUpdateHandler;
private Runnable statusUpdateRunnable;

public void onBackPressed() {
        if (backPressedOnce) {
            finish();
        }

        backPressedOnce = true;
        final Toast toast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT);
        toast.show();

        statusUpdateRunnable = new Runnable() {
            @Override
            public void run() {
                backPressedOnce = false;
                toast.cancel();  //Removes the toast after the exit.
            }
        };

        statusUpdateHandler.postDelayed(statusUpdateRunnable, 2000);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (statusUpdateHandler != null) {
        statusUpdateHandler.removeCallbacks(statusUpdateRunnable);
    }
}
Ecumenical answered 19/1, 2017 at 9:3 Comment(0)
G
2

For the activity whose is having Navigation Drawer, Use the following code for OnBackPressed()

boolean doubleBackToExitPressedOnce = false;

@Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            if (doubleBackToExitPressedOnce) {
                if (getFragmentManager().getBackStackEntryCount() ==0) {
                    finishAffinity();
                    System.exit(0);
                } else {
                    getFragmentManager().popBackStackImmediate();
                }
                return;
            }

            if (getFragmentManager().getBackStackEntryCount() ==0) {
                this.doubleBackToExitPressedOnce = true;
                Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

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

                    @Override
                    public void run() {
                        doubleBackToExitPressedOnce = false;
                    }
                }, 2000);
            } else {
                getFragmentManager().popBackStackImmediate();
            }
        }
    }
Goodson answered 12/7, 2017 at 19:47 Comment(0)
G
2

Here, I have generalized write the code for N tap counts. The code is similarly written for the Enable Developer option in android device phone. Even you can use this to enable features while developer testing the app.

 private Handler tapHandler;
 private Runnable tapRunnable;
 private int mTapCount = 0;
 private int milSecDealy = 2000;

onCreate(){
 ...
tapHandler = new Handler(Looper.getMainLooper());

 }

Call askToExit() on backpress or logout option.

private void askToExit() {
   if (mTapCount >= 2) {
    releaseTapValues();
    /* ========= Exit = TRUE  =========  */
   }

   mTapCount++;
   validateTapCount();
  }


  /* Check with null to avoid create multiple instances of the runnable */
  private void validateTapCount() {
   if (tapRunnable == null) {
    tapRunnable = new Runnable() {
     @Override
     public void run() {
      releaseTapValues();
      /* ========= Exit = FALSE  =========  */
     }
    };
    tapHandler.postDelayed(tapRunnable, milSecDealy);
   }
  }

  private void releaseTapValues() {
   /* Relase the value  */
   if (tapHandler != null) {
    tapHandler.removeCallbacks(tapRunnable);
    tapRunnable = null; /* release the object */
    mTapCount = 0; /* release the value */
   }
  }


  @Override
  protected void onDestroy() {
   super.onDestroy();
   releaseTapValues();
  }
Gelation answered 16/11, 2017 at 6:54 Comment(0)
P
2

I use this

import android.app.Activity;
import android.support.annotation.StringRes;
import android.widget.Toast;

public class ExitApp {

    private static long lastClickTime;

    public static void now(Activity ctx, @StringRes int message) {
        now(ctx, ctx.getString(message), 2500);
    }

    public static void now(Activity ctx, @StringRes int message, long time) {
        now(ctx, ctx.getString(message), time);
    }

    public static void now(Activity ctx, String message, long time) {
        if (ctx != null && !message.isEmpty() && time != 0) {
            if (lastClickTime + time > System.currentTimeMillis()) {
                ctx.finish();
            } else {
                Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
                lastClickTime = System.currentTimeMillis();
            }
        }
    }

}

use to in event onBackPressed

@Override
public void onBackPressed() {
   ExitApp.now(this,"Press again for close");
}

or ExitApp.now(this,R.string.double_back_pressed)

for change seconds need for close, specified miliseconds

ExitApp.now(this,R.string.double_back_pressed,5000)

Palmitate answered 27/11, 2017 at 18:52 Comment(0)
S
2

When HomeActivity contain navigation drawer and double backPressed() funtion to exit app. (Don't forget to initilize global variable boolean doubleBackToExitPressedOnce = false;) new handler after 2 sec set doubleBackPressedOnce variable to false

@Override
public void onBackPressed() {
    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.END)) {
        drawer.closeDrawer(GravityCompat.END);
    } else {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed();
            moveTaskToBack(true);
            return;
        } else {
            this.doubleBackToExitPressedOnce = true;
            Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    doubleBackToExitPressedOnce = false;
                }
            }, 2000);
        }
    }
}
Schizogony answered 29/11, 2017 at 6:50 Comment(0)
B
2

Most modern applications use just one activity and multiple fragments. So if you're using Navigation Components and needs to call implement that from the home fragment, here is the solution.

override fun onAttach(context: Context) {
    super.onAttach(context)
    val callback: OnBackPressedCallback = object :
    OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            if (doubleBackPressed) {
                activity.finishAffinity()
            }
            doubleBackPressed = true
            Toast.makeText(requireActivity(), "Press BACK again to exit", Toast.LENGTH_LONG).show()
            Handler(Looper.myLooper()!!).postDelayed(Runnable {doubleBackPressed = false},
                2000)
            }
        }
    requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
Bastardize answered 9/9, 2022 at 9:25 Comment(0)
E
2

I think this is the easiest method

private static long exit;
@override
public void onBackPressed() {
    if (exit + 2000 > System.currentTimeMillis()) super.onBackPressed();
    else
        Toast.makeText(getBaseContext(), "Press once again to exit!", Toast.LENGTH_SHORT).show();
    exit = System.currentTimeMillis();
}
Eleonoreleonora answered 30/1, 2023 at 17:41 Comment(1)
onBackpressed is already deprecatedPhotomap
O
1
boolean doubleBackToExitPressedOnce = false;

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;

    Snackbar.make(findViewById(R.id.photo_album_parent_view), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show();

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

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;
        }
    }, 2000);
}
Overbite answered 8/5, 2016 at 11:44 Comment(0)
H
1

Some Improvements in Sudheesh B Nair's answer, i have noticed it will wait for handler even while pressing back twice immediately, so cancel handler as shown below. I have cancled toast also to prevent it to display after app exit.

 boolean doubleBackToExitPressedOnce = false;
        Handler myHandler;
        Runnable myRunnable;
        Toast myToast;

    @Override
        public void onBackPressed() {
            if (doubleBackToExitPressedOnce) {
                myHandler.removeCallbacks(myRunnable);
                myToast.cancel();
                super.onBackPressed();
                return;
            }

            this.doubleBackToExitPressedOnce = true;
            myToast = Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT);
            myToast.show();

            myHandler = new Handler();

            myRunnable = new Runnable() {

                @Override
                public void run() {
                    doubleBackToExitPressedOnce = false;
                }
            };
            myHandler.postDelayed(myRunnable, 2000);
        }
Helianthus answered 28/5, 2016 at 6:11 Comment(0)
T
1

This is the same of the accepted and most voted response but this snipped used Snackbar instead of Toast.

boolean doubleBackToExitPressedOnce = false;

    @Override
    public void onBackPressed() {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed();
            return;
        }

        this.doubleBackToExitPressedOnce = true;
        Snackbar.make(content, "Please click BACK again to exit", Snackbar.LENGTH_SHORT)
                .setAction("Action", null).show();


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

            @Override
            public void run() {
                doubleBackToExitPressedOnce=false;
            }
        }, 2000);
    }
Tropicalize answered 2/2, 2017 at 14:28 Comment(0)
S
1

In my case, I've depend on Snackbar#isShown() for better UX.

private Snackbar exitSnackBar;

@Override
public void onBackPressed() {
    if (isNavDrawerOpen()) {
        closeNavDrawer();
    } else if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
        if (exitSnackBar != null && exitSnackBar.isShown()) {
            super.onBackPressed();
        } else {
            exitSnackBar = Snackbar.make(
                    binding.getRoot(),
                    R.string.navigation_exit,
                    2000
            );
            exitSnackBar.show();
        }
    } else {
        super.onBackPressed();
    }
}
Sideboard answered 24/2, 2017 at 6:52 Comment(0)
H
1

In this situation, Snackbar is the better option then Toast to display the quit action. Here is the method with snackbar that works.

@Override
        public void onBackPressed() {
            if (doubleBackToExitPressedOnce) {
                super.onBackPressed();
                return;
            }
            this.doubleBackToExitPressedOnce = true;
            Snackbar.make(this.getWindow().getDecorView().findViewById(android.R.id.content), "Please click BACK again to exit", Snackbar.LENGTH_SHORT).show();

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

                @Override
                public void run() {
                    doubleBackToExitPressedOnce=false;
                }
            }, 2000);
        }
Hydrogeology answered 25/1, 2018 at 16:34 Comment(0)
C
1

Here is another way... using the CountDownTimer method

private boolean exit = false;
@Override
public void onBackPressed() {
        if (exit) {
            finish();
        } else {
            Toast.makeText(this, "Press back again to exit",
                    Toast.LENGTH_SHORT).show();
            exit = true;
            new CountDownTimer(3000,1000) {

                @Override
                public void onTick(long l) {

                }

                @Override
                public void onFinish() {
                    exit = false;
                }
            }.start();
        }

    }
Cenesthesia answered 10/2, 2018 at 17:59 Comment(0)
H
1

In Kotlin on the back press to exit app you can use:

Define a global variable:

private var doubleBackToExitPressedOnce = false

Override onBackPressed:

override fun onBackPressed() {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed()
            return
        }

        doubleBackToExitPressedOnce = true
        Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_LONG).show()

        Handler().postDelayed({
            doubleBackToExitPressedOnce = false;
        }, 2000)
    }
Haunt answered 16/9, 2018 at 11:14 Comment(0)
S
1

Best Solution with Toast

In Java

private Toast exitToast;

@Override
public void onBackPressed() {
    if (exitToast == null || exitToast.getView() == null || exitToast.getView().getWindowToken() == null) {
        exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_LONG);
        exitToast.show();
    } else {
        exitToast.cancel();
        super.onBackPressed();
    }
}

In Kotlin

private var exitToast: Toast? = null

override fun onBackPressed() {
    if (exitToast == null || exitToast!!.view == null || exitToast!!.view.windowToken == null) {
        exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_LONG)
        exitToast!!.show()
    } else {
        exitToast!!.cancel()
        super.onBackPressed()
    }
}
Shelf answered 16/4, 2019 at 6:14 Comment(1)
Same as my answer, only 5 years late. Perhaps you wanted to upvote and comment on my answer? :)Loathly
U
1

The unique thing about this solution is the behaviour; in that a non double tap will show the toast and a successful double tap will show no toast while closing the app. The only disadvantage being the the showing of the toast will have a 650 millisecond delay. I believe this is the best solution with the best behaviour as logic dictates that it would be impossible to have this behaviour without such a delay

//App Closing Vars
private var doubleBackPressedInterval: Long = 650
private var doubleTap = false
private var pressCount = 0
private var timeLimit: Long = 0

override fun onBackPressed() {
    pressCount++
    if(pressCount == 1) {
        timeLimit = System.currentTimeMillis() + doubleBackPressedInterval
        if(!doubleTap) {
            showExitInstructions()
        }
    }
    if(pressCount == 2) {
        if(timeLimit > System.currentTimeMillis()) {
            doubleTap = true
            super.onBackPressed()
        }
        else {
            showExitInstructions()
        }
        pressCount = 1
        timeLimit = System.currentTimeMillis() + doubleBackPressedInterval
    }
}

private fun showExitInstructions() {
    Handler().postDelayed({
        if(!doubleTap) {
            Toast.makeText(this, "Try Agian", Toast.LENGTH_SHORT).show()
        }
    }, doubleBackPressedInterval)
}
Ultimogeniture answered 2/5, 2020 at 23:9 Comment(0)
P
1

This answer is easy to use but we need to double-tap to exit. I just modify the answer ,

    @Override
public void onBackPressed() {
    ++k;
    if(k==1){
        Toast.makeText(this, "Press back one more time to exit", Toast.LENGTH_SHORT).show();
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                --k;
            }
        },1000);
    }else{
        //do whatever you want to do on the click after the first for example:
        finishAffinity();
    }
}
Prophylaxis answered 17/8, 2021 at 11:53 Comment(0)
H
0

After having had to implement the same things many times, decided its about time someone built a simple to use library. And that is DoubleBackPress Android library. The README explains all the APIs provided along with Examples (like ToastDisplay + Exit Activity), but a short rundown of the steps here.


To get going, start by adding the dependency to your application :

dependencies {
    implementation 'com.github.kaushikthedeveloper:double-back-press:0.0.1'
} 

Next, create a DoubleBackPress object in your Activity which provides the required behaviour.

DoubleBackPress doubleBackPress = new DoubleBackPress();
doubleBackPress.setDoublePressDuration(3000);           // msec

Then create a Toast that needs to be shown upon the First Back Press. Here, you can create your own Toast, or go with Standard Toast provided in the library. Doing so here by the later option.

FirstBackPressAction firstBackPressAction = new ToastDisplay().standard(this);
doubleBackPress.setFirstBackPressAction(firstBackPressAction);   // set the action

Now, define what should happen when your Second Back Press happens. Here, we are closing the Activity.

DoubleBackPressAction doubleBackPressAction = new DoubleBackPressAction() {
    @Override
    public void actionCall() {
        finish();
        System.exit(0);
    }
};

Finally, override your back press behaviour with the DoubleBackPress behaviour.

@Override
public void onBackPressed() {
    doubleBackPress.onBackPressed();
}

Example GIF of similar behavioural requirements

Herwick answered 10/3, 2018 at 18:57 Comment(0)
M
0
 private static final int TIME_DELAY = 2000;
    private static long back_pressed;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onBackPressed() {
        if (back_pressed + TIME_DELAY > System.currentTimeMillis()) {
            super.onBackPressed();
        } else {
            Toast.makeText(getBaseContext(), "Press once again to exit!",
                    Toast.LENGTH_SHORT).show();
        }
        back_pressed = System.currentTimeMillis();
    }
Musicianship answered 15/3, 2018 at 5:59 Comment(0)
P
0

you can even make it more simple, and without using a hander, only do this =)

Long firstClick = 1L;
Long secondClick = 0L;

@Override
public void onBackPressed() {
secondClick = System.currentTimeMillis();
    if ((secondClick - firstClick) / 1000 < 2) {
          super.onBackPressed();
    } else {
          firstClick = System.currentTimeMillis();
          Toast.makeText(MainActivity.this, "click BACK again to exit", Toast.LENGTH_SHORT).show();
        }
 }
Paschasia answered 20/4, 2018 at 12:58 Comment(0)
B
0

You can also use the visibility of a Toast, so you don't need that Handler/postDelayed ultra solution.

Toast doubleBackButtonToast;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    doubleBackButtonToast = Toast.makeText(this, "Double tap back to exit.", Toast.LENGTH_SHORT);
}

@Override
public void onBackPressed() {
    if (doubleBackButtonToast.getView().isShown()) {
        super.onBackPressed();
    }

    doubleBackButtonToast.show();
}
Brancusi answered 4/5, 2018 at 20:13 Comment(0)
J
0
private static final int TIME_INTERVAL = 2000;
private long mBackPressed;
    @Override
        public void onBackPressed() {

            if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
                super.onBackPressed();
                Intent intent = new Intent(FirstpageActivity.this,
                        HomepageActivity.class);
                startActivity(intent);
                finish();

                return;
            } else {

                Toast.makeText(getBaseContext(),
                        "Tap back button twice  to go Home.", Toast.LENGTH_SHORT)
                        .show();

                mBackPressed = System.currentTimeMillis();

            }

        }
Jennine answered 19/7, 2018 at 6:3 Comment(0)
S
0

Here's my take on this :

 int oddeven = 0;
 long backBtnPressed1;
 long backBtnPressed2;
 @Override
 public void onBackPressed() {
     oddeven++;
     if(oddeven%2==0){
         backBtnPressed2 = System.currentTimeMillis();
         if(backBtnPressed2-backBtnPressed1<2000) {
            super.onBackPressed();
            return;
         }
     }
     else if(oddeven%2==1) { 
         backBtnPressed1 = System.currentTimeMillis();    
        //  Insert toast back button here
     }
 }
Spacing answered 25/12, 2018 at 21:41 Comment(1)
il try this I guess this is the LEAK-free solution.Fop
V
0

Here is a way to do it using RxJava:

override fun onCreate(...) {
    backPresses.timeInterval(TimeUnit.MILLISECONDS, Schedulers.io())
            .skip(1) //Skip initial event; delay will be 0.
            .onMain()
            .subscribe {
                if (it.time() < 7000) super.onBackPressed() //7000 is the duration of a Toast with length LENGTH_LONG.
            }.addTo(compositeDisposable)

    backPresses.throttleFirst(7000, TimeUnit.MILLISECONDS, Schedulers.io())
            .subscribe { Toast.makeText(this, "Press back again to exit.", LENGTH_LONG).show() }
            .addTo(compositeDisposable)
}

override fun onBackPressed() = backPresses.onNext(Unit)
Verret answered 16/1, 2019 at 15:9 Comment(0)
H
0

Back when Button pressed 2 times

public void click(View view){
    if (isBackActivated) {
        this.finish();
    }
    if (!isBackActivated) {
        isBackActivated = true;
        Toast.makeText(getApplicationContext(), "Again", Toast.LENGTH_SHORT).show();
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                isBackActivated = false;  // setting isBackActivated after 2 second
            }
        }, 2000);
    }

}
Hagler answered 31/1, 2019 at 6:31 Comment(0)
L
0

I've tried to create a utils class for this, so any activity or fragment can implement this to be simpler.

The code was written with Kotlin & has Java-interop as well.

I'm using coroutine to delay and reset the flag variable. But you can modify it to your needs.

Additional files: SafeToast.kt

lateinit var toast: Toast

fun Context.safeToast(msg: String, length: Int = Toast.LENGTH_LONG, action: (Context) -> Toast = default) {
    toast = SafeToast.makeText(this@safeToast, msg, length).apply {
        // do anything new here
        action(this@safeToast)
        show()
    }
}

fun Context.toastSpammable(msg: String) {
    cancel()
    safeToast(msg, Toast.LENGTH_SHORT)
}

fun Fragment.toastSpammable(msg: String) {
    cancel()
    requireContext().safeToast(msg, Toast.LENGTH_SHORT)
}

private val default: (Context) -> Toast = { it -> SafeToast.makeText(it, "", Toast.LENGTH_LONG) }

private fun cancel() {
    if (::toast.isInitialized) toast.cancel()
}

ActivityUtils.kt

@file:JvmMultifileClass
@file:JvmName("ActivityUtils")
package your.company.com

import android.app.Activity
import your.company.com.R
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch


private var backButtonPressedTwice = false

fun Activity.onBackPressedTwiceFinish() {
    onBackPressedTwiceFinish(getString(R.string.msg_back_pressed_to_exit), 2000)
}

fun Activity.onBackPressedTwiceFinish(@StringRes message: Int, time: Long) {
    onBackPressedTwiceFinish(getString(message), time)
}

fun Activity.onBackPressedTwiceFinish(message: String, time: Long) {
    if (backButtonPressedTwice) {
        onBackPressed()
    } else {
        backButtonPressedTwice = true
        toastSpammable(message)
        GlobalScope.launch {
            delay(time)
            backButtonPressedTwice = false
        }
    }
}

Usage in Kotlin

// ActivityA.kt
override fun onBackPressed() {
    onBackPressedTwiceFinish()
}

Usage in Java

@Override 
public void onBackPressed() {
    ActivityUtils.onBackPressedTwiceFinish()
}

This code was inspired by @webserveis here

Londrina answered 29/12, 2019 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.