Cómo pasar datos entre destinos

Navigation te permite adjuntar datos a una operación de navegación si defines los argumentos de un destino. Por ejemplo, el destino de un perfil de usuario podría tomar el argumento de ID del usuario para determinar a quién mostrar el contenido.

En general, debes optar por pasar solo la cantidad mínima de datos entre destinos. Por ejemplo, debes pasar una clave para recuperar un objeto en lugar de pasar el objeto en sí, ya que el espacio total para los estados guardados en Android es limitado. Si necesitas pasar grandes cantidades de datos, usa un objeto ViewModel como se describe en Descripción general de ViewModel.

Cómo definir argumentos de destino

Si deseas pasar datos entre destinos, primero define el argumento agregándolo al destino que lo recibe. Para ello, sigue los pasos que se indican a continuación:

  1. En el Editor de Navigation, haz clic en el destino que recibe el argumento.
  2. En el panel Attributes, haz clic en Add (+).
  3. En la ventana Add Argument Link, ingresa el nombre del argumento, el tipo, si es anulable y un valor predeterminado, si es necesario.
  4. Haz clic en Add. Observa que el argumento ahora aparece en la lista Arguments del panel Attributes.
  5. A continuación, haz clic en la acción correspondiente que te lleva al destino. En el panel Attributes, ahora deberías ver el argumento que acabas de agregar en la sección Argument Default Values.
  6. También puedes ver que el argumento se agregó en XML. Haz clic en la pestaña Text para activar o desactivar la vista XML, y observa que tu argumento se agregó al destino que recibe el argumento. A continuación, se muestra un ejemplo:

     <fragment android:id="@+id/myFragment" >
         <argument
             android:name="myArg"
             app:argType="integer"
             android:defaultValue="0" />
     </fragment>
    

Tipos de argumento compatibles

La biblioteca de Navigation admite los siguientes tipos de argumento:

Tipo Sintaxis app:argType Compatibilidad con valores predeterminados Administrado por rutas Anulable
Entero app:argType="integer" No
Flotante app:argType="float" No
Largo app:argType="long" Sí; los valores predeterminados siempre deben terminar con un sufijo "L" (p. ej., "123L") No
Booleano app:argType="boolean" Sí; valores "true" o "false" No
String app:argType="string"
Referencia del recurso app:argType="reference" Sí; los valores predeterminados deben tener el formato "@resourceType/resourceName" (p. ej., "@style/myCustomStyle") o "0" No
Parcelable personalizado app:argType="<type>", donde <type> es el nombre de clase completamente calificado de Parcelable Admite un valor predeterminado de "@null". No admite otros valores predeterminados. No
Serializable personalizado app:argType="<type>", donde <type> es el nombre de clase completamente calificado de Serializable Admite un valor predeterminado de "@null". No admite otros valores predeterminados. No
Enumeración personalizada app:argType="<type>", donde <type> es el nombre completamente calificado de la enum. Sí. Los valores predeterminados deben coincidir con el nombre no calificado (p. ej., "SUCCESS" para coincidir con MyEnum.SUCCESS). No No

Si un tipo de argumento admite valores nulos, puedes declarar un valor predeterminado nulo mediante android:defaultValue="@null".

Las rutas, los vínculos directos y los URI con sus argumentos se pueden analizar a partir de cadenas. Eso no es posible con tipos de datos personalizados como Parcelables y Serializables, como se ve en la tabla anterior. Para pasar datos complejos, almacena los datos en otro lugar, como ViewModel o una base de datos, y solo pasa un identificador mientras navegas; luego, recupera los datos en la ubicación nueva una vez que haya finalizado la navegación.

Cuando eliges uno de los tipos predeterminados, aparece el diálogo Select Class y te pide que elijas la clase correspondiente para ese tipo. La pestaña Project te permite elegir una clase de tu proyecto actual.

Puedes seleccionar <inferred type> para que la biblioteca de Navigation determine el tipo en función del valor provisto.

Puedes marcar Array para indicar que el argumento debería ser un array del valor Type seleccionado. Ten en cuenta lo siguiente:

  • No se admiten los arrays de enumeraciones y los de referencias a recursos.
  • Los arrays admiten valores anulables, independientemente de la compatibilidad con valores anulables del tipo subyacente. Por ejemplo, usar app:argType="integer[]" te permite usar app:nullable="true" para indicar que se acepta un array nulo.
  • Los arrays admiten un solo valor predeterminado: "@null". No admiten ningún otro.

Cómo anular un argumento de destino en una acción

Todas las acciones que navegan al destino usan valores predeterminados y argumentos a nivel de destino. Si es necesario, puedes anular el valor predeterminado de un argumento (o definir uno si todavía no existe). Para ello, define un argumento a nivel de la acción. Este argumento debe tener el mismo nombre y tipo que el declarado en el destino.

El siguiente código XML declara una acción con un argumento que anula el argumento a nivel de destino del ejemplo anterior:

<action android:id="@+id/startMyFragment"
    app:destination="@+id/myFragment">
    <argument
        android:name="myArg"
        app:argType="integer"
        android:defaultValue="1" />
</action>

Cómo usar Safe Args para pasar datos con seguridad de tipos

El componente de Navigation tiene un complemento de Gradle llamado "Safe Args" que genera clases de objetos y compiladores simples que permiten una navegación de tipo seguro y acceso a cualquier argumento asociado. Se recomienda el uso de Safe Args para navegar y pasar datos, ya que garantiza la seguridad de tipos.

Si no usas Gradle, no puedes usar el complemento Safe Args. En esas situaciones, puedes utilizar Bundles para pasar datos de forma directa.

Para agregar Safe Args a tu proyecto, incluye la siguiente classpath en tu archivo build.gradle de nivel superior:

Groovy

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.8.4"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Kotlin

buildscript {
    repositories {
        google()
    }
    dependencies {
        val nav_version = "2.8.4"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
    }
}

También debes aplicar uno de los dos complementos disponibles.

Para generar código de lenguaje Java adecuado para Java o módulos combinados de Java y Kotlin, agrega esta línea al archivo build.gradle de tu app o módulo:

Groovy

plugins {
  id 'androidx.navigation.safeargs'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs")
}

Como alternativa, para generar el código de Kotlin adecuado para módulos solo de Kotlin, agrega lo siguiente:

Groovy

plugins {
  id 'androidx.navigation.safeargs.kotlin'
}

Kotlin

plugins {
    id("androidx.navigation.safeargs.kotlin")
}

Tienes que tener el objeto android.useAndroidX=true en tu archivo gradle.properties, según se indica en Cómo migrar a AndroidX.

Después de habilitar Safe Args, el código generado contendrá las siguientes clases y métodos seguros para cada acción, además de cada destino de envío y recepción.

  • Se crea una clase por cada destino en el que se origina una acción. El nombre de esta clase es el nombre del destino de origen, unido a la palabra "Directions". Por ejemplo, si el destino de origen es un fragmento que se llama SpecifyAmountFragment, la clase generada se llamará SpecifyAmountFragmentDirections.

    Esa clase tiene un método para cada acción definida en el destino de origen.

  • Para cada acción que se usa para pasar el argumento, se crea una clase interna cuyo nombre se basa en la acción. Por ejemplo, si la acción se llama confirmationAction,, la clase se llamará ConfirmationAction. Si tu acción contiene argumentos sin un defaultValue, debes usar la clase de acción asociada para configurar el valor de los argumentos.

  • Se crea una clase para el destino de recepción. El nombre de esta clase es el nombre del destino, unido a la palabra "Args". Por ejemplo, si el fragmento de destino se llama ConfirmationFragment,, la clase generada se llamará ConfirmationFragmentArgs. Usa el método fromBundle() de esta clase para recuperar los argumentos.

En el siguiente ejemplo, se muestra cómo utilizar estos métodos para configurar un argumento y pasarlo al método navigate():

Kotlin

override fun onClick(v: View) {
   val amountTv: EditText = view!!.findViewById(R.id.editTextAmount)
   val amount = amountTv.text.toString().toInt()
   val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
   v.findNavController().navigate(action)
}

Java

@Override
public void onClick(View view) {
   EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount);
   int amount = Integer.parseInt(amountTv.getText().toString());
   ConfirmationAction action =
           SpecifyAmountFragmentDirections.confirmationAction();
   action.setAmount(amount);
   Navigation.findNavController(view).navigate(action);
}

En el código del destino de recepción, usa el método getArguments() para recuperar el paquete y usar su contenido. Cuando se usan las dependencias -ktx, los usuarios de Kotlin también pueden usar el delegado de propiedades by navArgs() para acceder a los argumentos.

Kotlin

val args: ConfirmationFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val tv: TextView = view.findViewById(R.id.textViewAmount)
    val amount = args.amount
    tv.text = amount.toString()
}

Java

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    TextView tv = view.findViewById(R.id.textViewAmount);
    int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount();
    tv.setText(amount + "");
}

Cómo usar Safe Args con una acción global

Cuando usas Safe Args con una acción global, debes proporcionar un valor android:id para su elemento raíz <navigation>, como se muestra en el siguiente ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/main_nav"
            app:startDestination="@id/mainFragment">

    ...

</navigation>

Navigation genera una clase Directions para el elemento <navigation> que se basa en el valor android:id. Por ejemplo, si tienes un elemento <navigation> con android:id=@+id/main_nav, la clase generada se llamará MainNavDirections. Todos los destinos dentro del elemento <navigation> generaron métodos para acceder a todas las acciones globales asociadas con los mismos métodos que se describieron en la sección anterior.

Cómo pasar datos entre los destinos con objetos Bundle

Si no utilizas Gradle, de todos modos, puedes pasar argumentos entre destinos con objetos Bundle. Crea un objeto Bundle y pásalo al destino con navigate(), como en el siguiente ejemplo:

Kotlin

val bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)

Java

Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);

En tu código de destino de recepción, usa el método getArguments() para recuperar Bundle y usar su contenido:

Kotlin

val tv = view.findViewById<TextView>(R.id.textViewAmount)
tv.text = arguments?.getString("amount")

Java

TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));

Cómo pasar datos al destino de inicio

Puedes pasar datos al destino de inicio de tu app. En primer lugar, debes compilar explícitamente un Bundle que contenga los datos. Luego, recurre a uno de los siguientes enfoques para pasar el Bundle al destino de inicio:

Para recuperar los datos de tu destino de inicio, llama a Fragment.getArguments().

Consideraciones de ProGuard

Si quieres reducir tu código, debes evitar la ofuscación de los nombres de clase Parcelable, Serializable y Enum como parte del proceso de reducción. Para eso, puedes usar uno de estos métodos:

  • Usa anotaciones @Keep.
  • Usa reglas keepnames.

Estos enfoques se describen en las siguientes subsecciones.

Cómo usar anotaciones @Keep

En el siguiente ejemplo, se agregan anotaciones @Keep a las definiciones de las clases de modelos:

Kotlin

@Keep class ParcelableArg : Parcelable { ... }

@Keep class SerializableArg : Serializable { ... }

@Keep enum class EnumArg { ... }

Java

@Keep public class ParcelableArg implements Parcelable { ... }

@Keep public class SerializableArg implements Serializable { ... }

@Keep public enum EnumArg { ... }

Cómo usar reglas keepnames

También puedes agregar reglas keepnames a tu archivo proguard-rules.pro, como se muestra en el siguiente ejemplo:

proguard-rules.pro

...

-keepnames class com.path.to.your.ParcelableArg
-keepnames class com.path.to.your.SerializableArg
-keepnames class com.path.to.your.EnumArg

...

Recursos adicionales

Para obtener más información acerca de la navegación, consulta los siguientes recursos adicionales.

Codelabs

Videos