Prácticas recomendadas de navegación para proyectos con varios módulos

Un gráfico de navegación puede constar de cualquier combinación de lo siguiente:

  • Un destino único, como un destino <fragment>
  • Un gráfico anidado que encapsula un conjunto de destinos relacionados
  • Un elemento <include>, que te permite incorporar otro archivo de gráfico de navegación como si estuviera anidado

Esta flexibilidad posibilita que combines gráficos de navegación más pequeños a fin de formar el gráfico de navegación completo de tu app, incluso si esos gráficos más pequeños se proporcionan en módulos de biblioteca independientes.

Para los ejemplos de este tema, cada módulo de biblioteca se enfoca en un atributo y proporciona un único gráfico de navegación que encapsula todos los destinos necesarios a los efectos de implementar esa función. En una app de producción, es posible que tengas muchos submódulos en un nivel inferior, que son detalles de implementación de este módulo de biblioteca de nivel superior. Cada uno de estos módulos de biblioteca se incluye de manera directa o indirecta en tu módulo app. La aplicación de varios módulos que se usa como ejemplo en este documento tiene la siguiente estructura:

arquitectura de la app para la app de ejemplo
el destino de inicio de la app de ejemplo
Figura 1: Arquitectura de la app y destino de inicio de la app de ejemplo.

Cada módulo de biblioteca es una unidad independiente con su propio gráfico de navegación y destinos. El módulo app depende de cada uno y los agrega como detalles de implementación en su archivo build.gradle, como se muestra a continuación:

dependencies {
    ...
    implementation project(":list")
    implementation project(":favorites")
    implementation project(":settings")

La función del módulo app

El módulo app se encarga de proporcionar el gráfico completo para tu app y de agregar el NavHost a la IU. Dentro del gráfico de navegación del módulo app, puedes hacer referencia a los gráficos de las bibliotecas mediante <include>. Si bien usar <include> es funcionalmente lo mismo que usar un gráfico anidado, <include> admite gráficos de otros módulos del proyecto o de proyectos de biblioteca, como se muestra en el siguiente ejemplo:

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

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />
</navigation>

Una vez que se incluye una biblioteca en el gráfico de navegación de nivel superior, puedes navegar por los gráficos de la biblioteca según sea necesario. Por ejemplo, puedes crear una acción para navegar al gráfico de configuración desde un fragmento de tu gráfico de navegación, como se muestra a continuación:

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

    <include app:graph="@navigation/list_navigation" />
    <include app:graph="@navigation/favorites_navigation" />
    <include app:graph="@navigation/settings_navigation" />

    <fragment
        android:id="@+id/random_fragment"
        android:name="com.example.android.RandomFragment"
        android:label="@string/fragment_random" >
        <!-- Launch into Settings Navigation Graph -->
        <action
            android:id="@+id/action_random_fragment_to_settings_nav_graph"
            app:destination="@id/settings_nav_graph" />
    </fragment>
</navigation>

Cuando varios módulos de biblioteca necesitan hacer referencia a un conjunto común de destinos, como un gráfico de acceso, no debes incluir esos destinos comunes en el gráfico de navegación de cada módulo de biblioteca. En su lugar, agrégalos al gráfico de navegación de tu módulo app. Luego, cada módulo de biblioteca podrá navegar por los módulos de biblioteca a fin de navegar a esos destinos comunes.

En el ejemplo anterior, la acción especifica un destino de navegación @id/settings_nav_graph. Este ID hace referencia a un destino definido dentro del gráfico incluido @navigation/settings_navigation.

La navegación de nivel superior en el módulo de la app

El componente Navigation incluye una clase NavigationUI. Esta clase contiene métodos estáticos que administran la navegación con la barra superior de la aplicación, el panel lateral de navegación y la navegación inferior. Si los destinos de nivel superior de tu app están compuestos por elementos de la IU proporcionados por módulos de biblioteca, el módulo app es un lugar natural para colocar los elementos de la IU y de la navegación de nivel superior. Como el módulo de la app depende de los módulos de biblioteca colaborativos, se puede acceder a todos sus destinos desde el código definido dentro del módulo de la app. Esto significa que puedes usar NavigationUI para vincular destinos a los elementos de menú si el ID del elemento coincide con el ID de un destino.

En la figura 2, el módulo app de ejemplo define una BottomNavigationView en su actividad principal. Los ID de elementos de menú coinciden con los ID de los gráficos de navegación correspondientes a los gráficos de bibliotecas:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/list_nav_graph"
        android:icon="@drawable/ic_list"
        android:title="List"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/favorites_nav_graph"
        android:icon="@drawable/ic_favorite"
        android:title="Favorites"
        app:showAsAction="ifRoom"/>

    <item
        android:id="@id/settings_nav_graph"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="ifRoom" />
</menu>

Para permitir que NavigationUI controle la navegación inferior, llama a setupWithNavController() desde onCreate() en tu clase de actividad principal como se muestra en el siguiente ejemplo:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    findViewById<BottomNavigationView>(R.id.bottom_nav)
            .setupWithNavController(navController)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    NavHostFragment navHostFragment =
            (NavHostFragment) supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
    NavController navController = navHostFragment.getNavController();
    BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);

    NavigationUI.setupWithNavController(bottomNav, navController);
}

Una vez implementado este código, NavigationUI navegará hasta el gráfico de biblioteca correspondiente cuando el usuario haga clic en un elemento de navegación inferior.

Ten en cuenta que no es una práctica recomendada que el módulo de tu app tenga una dependencia excesiva en un destino específico incorporado en lo profundo del gráfico de navegación de tus módulos de biblioteca. En la mayoría de los casos, querrás que el módulo de la app solo conozca el punto de entrada a cualquier gráfico de navegación incorporado o incluido (esto también se aplica fuera de los módulos de biblioteca). Si necesitas establecer un vínculo a un destino profundo dentro del gráfico de navegación de la biblioteca, la forma recomendada de hacerlo es mediante un vínculo directo. Los vínculos directos también son la única forma de que una biblioteca navegue a un destino en el gráfico de navegación de otra biblioteca.

La navegación por módulos de biblioteca

Durante el tiempo de compilación, los módulos de biblioteca independientes no pueden verse unos a otros, por lo que no puedes usar ID para navegar a destinos en otros módulos. En su lugar, usa un vínculo directo a fin de navegar directamente a un destino asociado con un vínculo directo implícito.

Para continuar con el ejemplo anterior, imagina que necesitas navegar de un botón del módulo de lista a un destino anidado en el módulo de configuración. Puedes hacer esto agregando un vínculo directo al destino en el gráfico de navegación de configuración, como se muestra a continuación:

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

    ...

    <fragment
        android:id="@+id/settings_fragment_two"
        android:name="com.example.google.login.SettingsFragmentTwo"
        android:label="@string/settings_fragment_two" >

        <deepLink
            app:uri="android-app://example.google.app/settings_fragment_two" />
    </fragment>
</navigation>

Luego, agrega el siguiente código al onClickListener del botón en el fragmento de la lista:

Kotlin

button.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("android-app://example.google.app/settings_fragment_two".toUri())
        .build()
    findNavController().navigate(request)
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        NavDeepLinkRequest request = NavDeepLinkRequest.Builder
            .fromUri(Uri.parse("android-app://example.google.app/settings_fragment_two"))
            .build();
        NavHostFragment.findNavController(this).navigate(request);
    }
});

A diferencia de la navegación con ID de acción o destino, puedes navegar a cualquier URI en cualquier gráfico, incluso entre módulos.

Cuando navegas con URI, no se restablece la pila de actividades. Este comportamiento difiere de la navegación de vínculo directo explícito, en la que se reemplaza la pila de actividades durante la navegación.