¿Cómo mostrar un cuadro de diálogo para confirmar que el usuario desea salir de una actividad de Android?

274

He estado tratando de mostrar un "¿Quieres salir?" tipo de diálogo cuando el usuario intenta salir de una actividad.

Sin embargo, no puedo encontrar los ganchos API apropiados. Activity.onUserLeaveHint()Inicialmente parecía prometedor, pero no puedo encontrar una manera de evitar que la Actividad termine.

Peter A
fuente
39
¿Es absolutamente esencial que tenga este aviso? Cuando un usuario desea finalizar una Actividad, debe poder hacerlo de inmediato. Es posible que desee repensar su estrategia.
Tom R
44
Mostrar una opción de confirmación de salida es obligatorio para su inclusión en la tienda de aplicaciones de Samsung. Una de mis aplicaciones fue rechazada por no tener esto.
John Ashmore
66
Eso es desagradable, @Samsung.
dokkaebi
3
Depende de lo que quieras hacer al salir. Si se conserva el estado y puede navegar hacia atrás, puede que no sea necesario mostrar un diálogo. Sin embargo, en una de mis aplicaciones limpio cookies, datos y cierro conexiones para siempre con una confirmación final de datos. El usuario debe ser consciente de la finalidad de su elección, especialmente porque hoy en día es poco común tener ese sentido de finalidad en una aplicación móvil.
Eric
15
@ Tom R: Totalmente en desacuerdo. Hay aplicaciones que no quieres terminar por accidente. Trabajan en el entorno empresarial, etc.
TomeeNS

Respuestas:

390

En Android 2.0+ esto se vería así:

@Override
public void onBackPressed() {
    new AlertDialog.Builder(this)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle("Closing Activity")
        .setMessage("Are you sure you want to close this activity?")
        .setPositiveButton("Yes", new DialogInterface.OnClickListener()
    {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            finish();    
        }

    })
    .setNegativeButton("No", null)
    .show();
}

En versiones anteriores se vería así:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    //Handle the back button
    if(keyCode == KeyEvent.KEYCODE_BACK) {
        //Ask the user if they want to quit
        new AlertDialog.Builder(this)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle(R.string.quit)
        .setMessage(R.string.really_quit)
        .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {

                //Stop the activity
                YourClass.this.finish();    
            }

        })
        .setNegativeButton(R.string.no, null)
        .show();

        return true;
    }
    else {
        return super.onKeyDown(keyCode, event);
    }

}
jax
fuente
15
También en 2.0 y versiones posteriores hay un nuevo evento onBackPressed que se recomienda sobre onKeyDown developer.android.com/intl/zh-TW/reference/android/app/… Aquí hay una sección que habla sobre los cambios y el nuevo enfoque recomendado. developer.android.com/intl/zh-TW/sdk/android-2.0.html
Patrick Kafka
3
Publicación de blog sobre la captura de la tecla de retroceso aquí: android-developers.blogspot.com/2009/12/… Tenga en cuenta que esto no le permite detectar otras formas en que el usuario puede salir de su aplicación: presionar inicio, seleccionar una notificación, recibir un teléfono llamada, etc.
hackbod
Esto funciona perfectamente, pero ¿cómo obliga a detener la ejecución del código mientras se muestra al usuario?
anon58192932
lo encontré, esta es una gran solución aquí: stackoverflow.com/questions/4381296/…
anon58192932
1
Este es el método () que estoy buscando pero no sabía dónde llamar a este método ().
usuario2841300
186
@Override
public void onBackPressed() {
    new AlertDialog.Builder(this)
           .setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   ExampleActivity.super.onBackPressed();
               }
           })
           .setNegativeButton("No", null)
           .show();
}
Chanakya Vadla
fuente
55
ElBradford hizo un comentario: Llamar al súper onBackPressed es mejor que asumir que onBackPressed solo llamará al final (). Incluso si eso es cierto ahora, puede no ser cierto en futuras APICustomTabActivity.super.onBackPressed
mplungjan
@mplungjan ¿Puedes aclarar tu comentario ... ¿Estás diciendo que es mejor reemplazar el finish()código super.onBackPressed()?
prohibición de geoingeniería
El comentario tiene 3 años. No tengo idea de qué es una buena práctica en 2015
mplungjan
@mplungjan Su comentario hacía referencia a futuras API, pero sería útil si pudiera aclararlo de todos modos.
prohibición de geoingeniería
No hay problema. Bueno, acabo de intentarlo MyActivity.super.onBackPressed();y funciona bien para mí. También parece el enfoque más lógico: llamar al método que está anulando si desea el comportamiento estándar cuando el usuario toca "No".
prohibición de geoingeniería
33

Modifiqué el código @ user919216 ... y lo hice compatible con WebView

@Override
public void onBackPressed() {
    if (webview.canGoBack()) {
        webview.goBack();

    }
    else
    {
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
       .setCancelable(false)
       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                finish();
           }
       })
       .setNegativeButton("No", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
           }
       });
AlertDialog alert = builder.create();
alert.show();
    }

}
suraj jain
fuente
22

Prefiero salir con un doble toque en el botón Atrás que con un Diálogo de salida.

En esta solución, muestra un brindis cuando regresa por primera vez, advirtiendo que otra presión de retroceso cerrará la aplicación. En este ejemplo menos de 4 segundos.

private Toast toast;
private long lastBackPressTime = 0;

@Override
public void onBackPressed() {
  if (this.lastBackPressTime < System.currentTimeMillis() - 4000) {
    toast = Toast.makeText(this, "Press back again to close this app", 4000);
    toast.show();
    this.lastBackPressTime = System.currentTimeMillis();
  } else {
    if (toast != null) {
    toast.cancel();
  }
  super.onBackPressed();
 }
}

Token de: http://www.androiduipatterns.com/2011/03/back-button-behavior.html

Toni Gamez
fuente
Este es definitivamente mi favorito, ya que el cuadro de diálogo puede ser engorroso. Lo cambié ligeramente para hacerlo 3 segundos y parece un poco más natural con ese marcador. Gracias por la publicacion.
PGMacDesign
en la prensa de doble vuelta idealmente aplicación debe salir conmigo, pero en mi código es abierto una actividad de inicio de sesión (mediante copia de la actividad con el hilandero habilitado)
techDigi
21

Si no está seguro de si la llamada a "volver" saldrá de la aplicación, o llevará al usuario a otra actividad, puede ajustar las respuestas anteriores en un cheque, isTaskRoot (). Esto puede suceder si su actividad principal se puede agregar a la pila de respaldo varias veces, o si está manipulando su historial de pila de respaldo.

if(isTaskRoot()) {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Are you sure you want to exit?")
       .setCancelable(false)
       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                YourActivity.super.onBackPressed;
           }
       })
       .setNegativeButton("No", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
           }
       });
    AlertDialog alert = builder.create();
    alert.show();

} else {
    super.onBackPressed();
}
Alegría
fuente
5

Usando Lambda:

    new AlertDialog.Builder(this).setMessage(getString(R.string.exit_msg))
        .setTitle(getString(R.string.info))
        .setPositiveButton(getString(R.string.yes), (arg0, arg1) -> {
            moveTaskToBack(true);
            finish();
        })
        .setNegativeButton(getString(R.string.no), (arg0, arg1) -> {
        })
        .show();

También necesita establecer un lenguaje de nivel para admitir Java 8 en su gradle.build:

compileOptions {
       targetCompatibility 1.8
       sourceCompatibility 1.8
}
vasiljevski
fuente
5

en China, la mayoría de las aplicaciones confirmarán la salida haciendo "clic dos veces":

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().postDelayed(new Runnable() {

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 
Siwei Shen 申思维
fuente
5

Primero elimine super.onBackPressed();del onbackPressed()método que y debajo del código:

@Override
public void onBackPressed() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    MyActivity.this.finish();
               }
           })
           .setNegativeButton("No", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
               }
           });
    AlertDialog alert = builder.create();
    alert.show();

}
Anjali-Systematix
fuente
2

Me gusta un enfoque @GLee y usarlo con un fragmento como el siguiente.

@Override
public void onBackPressed() {
    if(isTaskRoot()) {
        new ExitDialogFragment().show(getSupportFragmentManager(), null);
    } else {
        super.onBackPressed();
    }
}

Diálogo usando Fragmento:

public class ExitDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.exit_question)
            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    getActivity().finish();
                }
            })
            .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    getDialog().cancel();
                }
            })
            .create();
    }
}
marioosh
fuente
2
Just put this code in your first activity 

@Override
    public void onBackPressed() {
        if (drawerLayout.isDrawerOpen(GravityCompat.END)) {
            drawerLayout.closeDrawer(GravityCompat.END);
        }
        else {
// if your using fragment then you can do this way
            int fragments = getSupportFragmentManager().getBackStackEntryCount();
            if (fragments == 1) {
new AlertDialog.Builder(this)
           .setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    finish();
               }
           })
           .setNegativeButton("No", null)
           .show();


            } else {
                if (getFragmentManager().getBackStackEntryCount() > 1) {
                    getFragmentManager().popBackStack();
                } else {

           super.onBackPressed();
                }
            }
        }
    }
Mohit Hooda
fuente
Mi primera actividad, no puedo ponerla en actividad en el medio. porque ahora cierra la actividad actual y muestra la anterior
shareef
1

Otra alternativa sería mostrar un Toast/ Snackbaren la primera pulsación hacia atrás pidiendo presionar nuevamente para salir , lo cual es mucho menos intrusivo que mostrar una AlertDialogpara confirmar si el usuario desea salir de la aplicación.

Puede usar el DoubleBackPress Android Librarypara lograr esto con unas pocas líneas de código. Ejemplo de GIF que muestra un comportamiento similar.

Para comenzar, agregue la dependencia a su aplicación:

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

Luego, en su Actividad, implemente el comportamiento requerido.

// set the Toast to be shown on FirstBackPress (ToastDisplay - builtin template)
// can be replaced by custom action (new FirstBackPressAction{...})
FirstBackPressAction firstBackPressAction = new ToastDisplay().standard(this);

// set the Action on DoubleBackPress
DoubleBackPressAction doubleBackPressAction = new DoubleBackPressAction() {
    @Override
    public void actionCall() {
        // TODO : Exit the application
        finish();
        System.exit(0);
    }
};

// setup DoubleBackPress behaviour : close the current Activity
DoubleBackPress doubleBackPress = new DoubleBackPress()
        .withDoublePressDuration(3000)     // msec - wait for second back press
        .withFirstBackPressAction(firstBackPressAction)
        .withDoubleBackPressAction(doubleBackPressAction);

Finalmente, establezca esto como el comportamiento en presionar hacia atrás.

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