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 te permite combinar gráficos de navegación más pequeños para 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 independientes.

Para los ejemplos de este tema, cada módulo de funciones se enfoca en una función y proporciona un único gráfico de navegación que encapsula todos los destinos necesarios a los efectos de implementar esa función. Para las apps en producción, es posible que tengas muchos submódulos en un nivel inferior que son detalles de implementación de este módulo de funciones de nivel superior. Cada uno de estos módulos de funciones se incluyen 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:

gráfico de dependencias para una aplicación de varios módulos de muestra
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 funciones es una unidad independiente que tiene 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:

Groovy

dependencies {
    ...
    implementation project(":feature:home")
    implementation project(":feature:favorites")
    implementation project(":feature:settings")

Kotlin

dependencies {
    ...
    implementation(project(":feature:home"))
    implementation(project(":feature:favorites"))
    implementation(project(":feature:settings"))

El rol 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 con <include>. Si bien <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/home_nav_graph">

    <include app:graph="@navigation/home_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/home_nav_graph">

    <include app:graph="@navigation/home_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 funciones 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 funciones. En su lugar, agrégalos al gráfico de navegación de tu módulo app. Cada módulo de funciones puede navegar entre los módulos de funciones para 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 app, 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 funciones, 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 funciones colaborativos, se puede acceder a todos sus destinos desde el código definido dentro del módulo de la app. Eso 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 IDs de elementos de menú coinciden con los IDs 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/home_nav_graph"
        android:icon="@drawable/ic_home"
        android:title="Home"
        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 funciones. 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 funciones). 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.

Cómo navegar entre los módulos de funciones

Durante el tiempo de compilación, los módulos de funciones independientes no pueden verse unos a otros, por lo que no puedes usar IDs para navegar a destinos en otros módulos. En su lugar, usa un vínculo directo para 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 :feature:home a un destino anidado en el módulo de :feature:settings. Puedes hacerlo si agregas un vínculo directo al destino en el gráfico de navegación de configuración, como se muestra aquí:

<?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 inicio:

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 IDs 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.