Cómo brindar navegación hacia atrás de manera correcta

La navegación hacia atrás se refiere a cómo los usuarios navegan de manera inversa por el historial de pantallas que visitaron anteriormente. Todos los servicios de Android incluyen el botón Atrás para este tipo de navegación, por lo que tu app no debería incluirlo en la IU.

En casi todas las situaciones, el sistema mantiene una pila de actividades mientras el usuario navega por la app. De esta manera, el sistema puede regresar a la pantalla anterior sin inconvenientes cuando el usuario presiona el botón Atrás. Sin embargo, existen algunos casos en los que la app debe especificar manualmente el comportamiento del botón Atrás a fin de brindar una mejor experiencia del usuario.

También puedes consultar Diseño de navegación hacia atrás y hacia arriba, Tareas y pila de actividades, y Diseño de Android: navegación.

Entre los patrones de navegación que requieren que especifiques de forma manual el comportamiento del botón Atrás, se incluyen los siguientes:

En las siguientes secciones, se describe cómo implementar la navegación hacia atrás de manera correcta.

Sintetiza una nueva pila de actividades para vínculos directos

Por lo general, el sistema crea la pila de actividades de manera incremental a medida que el usuario navega de una actividad a la siguiente. Sin embargo, cuando el usuario ingresa a tu app mediante un vínculo directo que inicia la actividad en su propia tarea, es necesario que sintetices una nueva pila de actividades, dado que se está ejecutando la actividad en una nueva tarea sin ninguna pila de actividades.

Por ejemplo, cuando una notificación dirige al usuario a una actividad en lo más profundo de la jerarquía de la app, debes agregar actividades en la pila de tu tarea, de manera que al presionar Atrás navegue hacia arriba en la jerarquía de la app en lugar de salir de ella. Este patrón se describe con mayor profundidad en la Guía de diseño de navegación.

Especifica actividades principales en el manifiesto

A partir de Android 4.1 (nivel de API 16), puedes declarar el elemento lógico principal de cada actividad especificando el atributo android:parentActivityName en el elemento <activity>. De esta manera, el sistema facilita los patrones de navegación, ya que esos datos le permiten determinar la ruta lógica de navegación hacia atrás o hacia arriba.

Si tu app admite Android 4.0 o versiones anteriores, incluye la Biblioteca de compatibilidad y agrega el elemento <meta-data> dentro de <activity>. Luego, especifica la actividad principal como valor de android.support.PARENT_ACTIVITY, que coincida con el atributo android:parentActivityName.

Por ejemplo:

<application ... >
    ...
    <!-- The main/home activity (it has no parent activity) -->
    <activity
        android:name="com.example.myfirstapp.MainActivity" ...>
        ...
    </activity>
    <!-- A child of the main activity -->
    <activity
        android:name="com.example.myfirstapp.DisplayMessageActivity"
        android:label="@string/title_activity_display_message"
        android:parentActivityName="com.example.myfirstapp.MainActivity" >
        <!-- The meta-data element is needed for versions lower than 4.1 -->
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.myfirstapp.MainActivity" />
    </activity>
</application>

Si declaras la actividad principal de esta manera, puedes usar las API de NavUtils para sintetizar una nueva pila de actividades al identificar qué actividad principal es apropiada para cada caso.

Crea la pila de actividades al comenzar la actividad

El agregado de actividades a la pila comienza a partir de que el evento dirige al usuario a tu app. Es decir que, en lugar de llamar a startActivity(), usa las API de TaskStackBuilder para definir qué actividades se deben ubicar en una nueva pila de actividades. Luego, inicia la actividad objetivo llamando a startActivities() o crea el PendingIntent adecuado llamando a getPendingIntent().

Por ejemplo, cuando una notificación dirige al usuario a una actividad en lo profundo de la jerarquía de la app, puedes usar este código para crear un PendingIntent que inicie una actividad y, luego, inserte una nueva pila de actividades en la tarea objetivo:

Kotlin

val detailsIntent = Intent(this, DetailsActivity::class.java)

val pendingIntent: PendingIntent? = TaskStackBuilder.create(this)
        // add all of DetailsActivity's parents to the stack,
        // followed by DetailsActivity itself
        .addNextIntentWithParentStack(detailsIntent)
        .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)

val builder = NotificationCompat.Builder(this)
        .setContentIntent(pendingIntent)
...

Java

// Intent for the activity to open when user selects the notification
Intent detailsIntent = new Intent(this, DetailsActivity.class);

// Use TaskStackBuilder to build the back stack and get the PendingIntent
PendingIntent pendingIntent =
        TaskStackBuilder.create(this)
                        // add all of DetailsActivity's parents to the stack,
                        // followed by DetailsActivity itself
                        .addNextIntentWithParentStack(detailsIntent)
                        .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent);
...

El PendingIntent resultante especifica no solo la actividad que se iniciará (como lo definió detailsIntent), sino también la pila de actividades que se debe insertar en la tarea (todas las actividades principales de DetailsActivity definidas por detailsIntent). Por lo tanto, cuando se inicie DetailsActivity al presionar Atrás, se navegará de forma inversa pasando por cada una de las actividades principales de la clase DetailsActivity.

Nota: Para que funcione correctamente el método addNextIntentWithParentStack(), debes declarar el elemento lógico principal de cada una de las actividades en el archivo del manifiesto mediante el atributo android:parentActivityName (y su elemento <meta-data> correspondiente), como se describe más arriba.

Implementa la navegación hacia atrás por fragmentos

Cuando uses fragmentos en tu app, es posible que los objetos FragmentTransaction individuales representen cambios de contexto que se deberían agregar a la pila de actividades. Por ejemplo, si implementas un flujo principal o de detalles en un auricular al cambiar los fragmentos, debes asegurarte de que, al presionar el botón Atrás en la pantalla de detalles, el usuario vuelva a la pantalla principal. Para ello, llama a addToBackStack() antes de realizar la transacción:

Kotlin

// Works with either the framework FragmentManager or the
// support package FragmentManager (supportFragmentManager).
supportFragmentManager.beginTransaction()
        .add(detailFragment, "detail")
        // Add this transaction to the back stack
        .addToBackStack(null)
        .commit()

Java

// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
                           .add(detailFragment, "detail")
                           // Add this transaction to the back stack
                           .addToBackStack(null)
                           .commit();

Cuando hay objetos FragmentTransaction en la pila de actividades y el usuario presiona el botón Atrás, FragmentManager quita la transacción más reciente de la pila de actividades y realiza la acción inversa (como quitar un fragmento si la transacción lo agregó).

Nota: No agregues transacciones a la pila de actividades cuando la transacción sea para navegación horizontal (por ejemplo, cuando se cambia de pestaña) o cuando se modifica la apariencia del contenido (por ejemplo, al ajustar filtros). Para obtener más información sobre cuándo se debe realizar la navegación hacia Atrás, consulta la Guía de diseño de navegación.

Si tu aplicación actualiza otros elementos de la interfaz de usuario para reflejar el estado actual de los fragmentos (como la barra de acción), recuerda actualizar la IU cuando realices la transacción. Debes actualizar la interfaz de usuario cuando se modifica la pila de actividades, además de cuando realizas la transacción. Puedes escuchar cuando un FragmentTransaction se revierte al configurar un FragmentManager.OnBackStackChangedListener:

Kotlin

supportFragmentManager.addOnBackStackChangedListener {
    // Update your UI here.
}

Java

getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });

Implementa la navegación hacia atrás para WebViews

Si una parte de tu app está dentro de una WebView, podría ser apropiado que el botón Atrás atraviese todo el historial de navegación. Para ello, anula onBackPressed() y el proxy de WebView si tiene el estado de historial:

Kotlin

override fun onBackPressed() {
    if (mWebView.canGoBack()) {
        mWebView.goBack()
    } else {
        // Otherwise defer to system default behavior.
        super.onBackPressed()
    }
}

Java

@Override
public void onBackPressed() {
    if (mWebView.canGoBack()) {
        mWebView.goBack();
        return;
    }

    // Otherwise defer to system default behavior.
    super.onBackPressed();
}

Ten cuidado cuando uses este mecanismo con páginas web altamente dinámicas que generan un historial más extenso. Es posible que este tipo de páginas, como aquellas que realizan cambios con frecuencia en el hash del documento, hagan que salir de tu actividad sea una tarea tediosa para los usuarios.

Para obtener más información sobre cómo usar WebView, consulta cómo crear apps web en WebView.