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

Jonathan Soifer
  • 2,715
  • 6
  • 27
  • 50
Guillaume
  • 22,694
  • 14
  • 56
  • 70
  • [Android - Confirm app exit with toast] [1]: http://stackoverflow.com/questions/14006461/android-confirm-app-exit-with-toast/18654014#18654014 – resource8218 Sep 06 '13 at 09:12
  • 1
    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. – Sohayb Hassoun Sep 11 '13 at 14:44
  • Other solution http://stackoverflow.com/questions/8430805/android-clicking-twice-the-back-button-to-exit-activity – Codelaby Feb 17 '16 at 15:19
  • 1
    Possible duplicate of [clicking on the android back button twice to exit the app](https://stackoverflow.com/questions/26581427/clicking-on-the-android-back-button-twice-to-exit-the-app) – Prabh deep Jun 03 '19 at 05:03

48 Answers48

1062

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.

Nikunj Paradva
  • 15,332
  • 5
  • 54
  • 65
Sudheesh B Nair
  • 11,023
  • 1
  • 16
  • 13
  • 50
    Best answer! You may also add condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) { in case of Fragment-based add – Anton Derevyanko Dec 12 '12 at 10:47
  • 2
    I agree, this is definitely the best answer and should be the accepted answer. – BruceHill Feb 24 '13 at 18:27
  • 3
    You should remove the Runnable when exit application. – Wayne Apr 22 '13 at 10:05
  • Check my answer.I have modified the answer given by `Sudheesh B Nair` to cover the above suggested comments. – Mehul Joisar Feb 03 '14 at 07:46
  • 22
    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. – Saro Taşciyan Apr 27 '14 at 10:57
  • @Zefnus, Why ? Please explain for the beginners like me. – partho Apr 12 '16 at 07:43
  • @partho 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. – Saro Taşciyan Apr 12 '16 at 07:50
  • if i add this code block me getting error in handler – R.Anandan Apr 27 '17 at 12:40
  • Handler must be handled with care now. If user presses back, it might leak. – Chintan Rathod Oct 04 '18 at 10:45
  • I personally like using a [Snackbar](https://developer.android.com/reference/android/support/design/widget/Snackbar) instead of a Toast. The Toast persists on the screen for some extra time after the app is closed despite using `LENGTH_SHORT` – Kathir Nov 30 '18 at 13:17
  • 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. – Rahul Raina Jun 07 '20 at 09:32
254

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.

Community
  • 1
  • 1
Saro Taşciyan
  • 5,210
  • 5
  • 31
  • 50
  • 12
    For the ones who think that it's the same what Sudheesh B Nair said: Same functionality, better performance. so +1. – Bedir Yilmaz Mar 11 '14 at 17:52
  • 4
    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? – acrespo Jul 01 '14 at 21:32
  • @acrespo 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. – Saro Taşciyan Jul 02 '14 at 17:06
  • 1
    @joonty thanks for edit, that **int** keyword has been missing for some time. It will compile now (: – Saro Taşciyan Oct 17 '14 at 06:50
  • 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. – NSouth Apr 23 '15 at 23:25
  • 2
    @NSouth 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. – Saro Taşciyan Apr 24 '15 at 07:26
  • @NSouth 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. – Saro Taşciyan May 06 '15 at 19:13
  • 1
    @acrespo I suppose we have a solid solution for _toast message persisting after the application closes_ now. – Saro Taşciyan May 06 '15 at 19:15
  • @Zefnus : Don't you think that my answer is almost same as yours? I couldn't find any significant reason to add new one. – Mehul Joisar Sep 08 '15 at 05:05
  • 1
    @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. – Saro Taşciyan Sep 08 '15 at 07:13
  • @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) – Srikar Reddy Oct 10 '15 at 16:59
  • 1
    Now that's the kind of answer I really appreciate. Very well explained and works efficiently! +1 for the extra thought! – Emzor Jan 29 '16 at 00:37
  • Shouldn't `mBackPressed` be initialized? – Thomas Wana Feb 11 '16 at 22:32
  • 1
    Since this answer is arguably better than the accepted answer, I'd like to echo [Anton's comment on the accepted answer](http://stackoverflow.com/questions/8430805/android-clicking-twice-the-back-button-to-exit-activity#comment19046108_13578600): "*You may also add condition `if (fragmentManager.getBackStackEntryCount() != 0) {` in case of Fragment-based add*" – Andrew T. Mar 09 '16 at 05:38
  • This is a best answer – Codelaby Sep 09 '16 at 08:47
  • @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)`. – ToolmakerSteve Sep 22 '16 at 10:40
  • the first block is simple and best solution – wanz Jul 28 '17 at 06:26
  • this answer is the best +1 – Tobi Oyelekan Dec 25 '19 at 08:06
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.

Guillaume
  • 22,694
  • 14
  • 56
  • 70
  • 20
    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. – BruceHill Feb 24 '13 at 18:26
  • 1
    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. – Ferran Maylinch Feb 20 '14 at 12:47
  • 4
    @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. – ToolmakerSteve Sep 22 '16 at 10:45
  • IMHO, better variants on this [here](http://stackoverflow.com/a/14006485/199364) and [here](http://stackoverflow.com/a/15728990/199364). – ToolmakerSteve Sep 22 '16 at 10:48
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.

Chintan Rathod
  • 25,864
  • 13
  • 83
  • 93
26

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;
}
vHow
  • 711
  • 7
  • 6
  • 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. – ravemir Jan 16 '13 at 10:04
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();
    }
}
Hugo Passos
  • 7,719
  • 2
  • 35
  • 52
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 !!

Mehul Joisar
  • 15,348
  • 6
  • 48
  • 57
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

Rakshith
  • 387
  • 4
  • 24
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.

Max Lee
  • 189
  • 1
  • 4
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);
    }
Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
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?

Community
  • 1
  • 1
Caner
  • 57,267
  • 35
  • 174
  • 180
  • Point taken. By exit, I meant "going back to home screen" – Guillaume Dec 08 '11 at 12:14
  • 3
    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. – Caner Dec 08 '11 at 12:26
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();
   }
}
Community
  • 1
  • 1
Kartik
  • 7,677
  • 4
  • 28
  • 50
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.

Jan
  • 2,060
  • 2
  • 29
  • 34
Ashik
  • 1,035
  • 12
  • 15
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)

        }
    }
Praveen
  • 946
  • 6
  • 14
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!

SanThee
  • 2,301
  • 3
  • 23
  • 35
Chris Rohit Brendan
  • 893
  • 1
  • 9
  • 20
  • 3
    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 too – mamba4ever Apr 21 '13 at 04:10
  • 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 timing – Gastón Saillén Dec 11 '18 at 19:45
5

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);
}
Tomasz Nguyen
  • 2,561
  • 22
  • 25
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
DannyRitiu
  • 91
  • 6
  • 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. – Onuoha Chukwuemeka Charles Sep 09 '22 at 08:10
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();
}
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
Bu Ti
  • 31
  • 1
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();
    }
}
Community
  • 1
  • 1
k8C
  • 414
  • 4
  • 9
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);
} 
RubioRic
  • 2,442
  • 4
  • 28
  • 35
Arpit Patel
  • 7,212
  • 5
  • 56
  • 67
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

Suraj Vaishnav
  • 7,777
  • 4
  • 43
  • 46
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);
}
slfan
  • 8,950
  • 115
  • 65
  • 78
Sai Aditya
  • 2,353
  • 1
  • 14
  • 16
  • 1
    How does this even handle the double press scenario? As soon as I hit back, this launches an activity. – kilokahn Jan 19 '18 at 08:57
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);
    }
}
Saket Kumar
  • 463
  • 8
  • 22
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();
            }
        }
    }
Priya Rajan
  • 687
  • 8
  • 21
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();
  }
Chintan Khetiya
  • 15,962
  • 9
  • 47
  • 85
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)

Codelaby
  • 2,604
  • 1
  • 25
  • 25
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);
        }
    }
}
Abhishek S
  • 105
  • 3
  • 12
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)
}
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();
}
Nikunj Paradva
  • 15,332
  • 5
  • 54
  • 65
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);
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
maniix
  • 51
  • 3
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);
        }
Divyang Panchal
  • 1,889
  • 1
  • 19
  • 27
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);
    }
MobileOS
  • 13
  • 6
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();
    }
}
Kasim Rangwala
  • 1,765
  • 2
  • 23
  • 44
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);
        }
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();
        }

    }
Anuj Sain
  • 163
  • 3
  • 9
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)
    }
Shailendra Madda
  • 20,649
  • 15
  • 100
  • 138
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()
    }
}
Karthik Kompelli
  • 2,104
  • 1
  • 19
  • 22
  • Same as [my answer](https://stackoverflow.com/a/23126219/1039555), only 5 years late. Perhaps you wanted to upvote and comment on my answer? :) – Kartik Apr 16 '19 at 06:16
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)
}
user2288580
  • 2,210
  • 23
  • 16
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();
    }
}
Ganesh MB
  • 1,109
  • 2
  • 14
  • 27
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

Kaushik NP
  • 6,733
  • 9
  • 31
  • 60
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();
    }
Avinash
  • 361
  • 4
  • 16
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();
        }
 }
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();
}
Hudson Pereira
  • 1,066
  • 8
  • 13
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();

            }

        }
Umesh Maharjan
  • 312
  • 2
  • 6
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
     }
 }
R41N
  • 1
  • 3
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)
urgentx
  • 3,832
  • 2
  • 19
  • 30
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);
    }

}
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

mochadwi
  • 1,190
  • 9
  • 32
  • 87