Práticas recomendadas de navegação para projetos com vários módulos

Um gráfico de navegação pode consistir em qualquer combinação dos itens a seguir:

  • Um destino só, como <fragment>.
  • Um gráfico aninhado que encapsula um conjunto de destinos relacionados.
  • Um elemento <include>, que permite incorporar outro arquivo de gráfico de navegação como se ele estivesse aninhado.

Essa flexibilidade permite combinar gráficos de navegação menores para formar o gráfico de navegação completo do app, mesmo que esses gráficos menores sejam fornecidos por módulos separados.

Para os exemplos deste tópico, cada módulo de recursos será focado em torno de um recurso e fornece um único gráfico de navegação que encapsula todos os destinos necessárias para implementar esse recurso. Em um app de produção, você pode ter muitos submódulos em um nível mais baixo, que são detalhes de implementação do módulo de recursos de nível mais alto. Cada um desses módulos é incluído, direta ou indiretamente, no módulo do app. O aplicativo de exemplo com vários módulos usado neste documento tem esta estrutura:

gráfico de dependência de um aplicativo de exemplo com vários módulos
o destino inicial do app de exemplo
Figura 1. Arquitetura do app e destino inicial do app de exemplo.

Cada módulo de recursos é uma unidade independente com um gráfico de navegação e destinos próprios. O módulo app depende deles, adicionando-os como detalhes de implementação no arquivo build.gradle, conforme mostrado abaixo:

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"))

O papel do módulo app

O módulo do app é responsável por fornecer o gráfico completo do app e adicionar o NavHost à IU. No gráfico de navegação do módulo do app, você pode referenciar os gráficos da biblioteca usando <include>. Embora o uso de <include> seja funcionalmente o mesmo que o uso de um gráfico aninhado, <include> oferece suporte a gráficos de outros módulos do projeto ou de projetos de biblioteca, como mostrado no exemplo abaixo:

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

Quando uma biblioteca for incluída no gráfico de navegação de nível superior, vai ser possível navegar pelos gráficos da biblioteca conforme necessário. Por exemplo, você pode criar uma ação para navegar até o gráfico de configurações de um fragmento no gráfico de navegação, conforme mostrado abaixo:

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

Quando vários módulos de biblioteca precisam referenciar um conjunto comum de destinos, como um gráfico de login, não é possível incluir esses destinos comuns no gráfico de navegação de cada módulo de recursos. Em vez disso, adicione esses destinos comuns ao gráfico de navegação do módulo app. Dessa forma, cada módulo de recursos pode navegar pelos módulos para ir até esses destinos comuns.

No exemplo anterior, a ação especifica um destino de navegação de @id/settings_nav_graph. Esse ID se refere a um destino definido no gráfico incluído @navigation/settings_navigation.

Navegação de nível superior no módulo do app

O componente de navegação inclui uma classe NavigationUI. Essa classe contém métodos estáticos que gerenciam a navegação com a barra de apps de cima, a gaveta de navegação e a navegação inferior. Se os destinos de nível mais alto do app forem compostos por elementos da IU fornecidos por módulos de recursos, o módulo app vai ser um lugar natural para colocar os elementos da IU e a navegação de nível mais alto. Como o módulo do app depende dos módulos de recursos colaborativos, todos os destinos são acessíveis no código definido no módulo do app. Isso significa que você pode usar a NavigationUI para associar destinos a itens de menu quando o ID do item corresponde ao ID de um destino.

Na figura 2, o módulo app de exemplo define uma BottomNavigationView na atividade principal. Os IDs de item de menu correspondem aos IDs dos gráficos de navegação da biblioteca:

<?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 gerencie a navegação inferior, chame setupWithNavController() em onCreate() na sua classe de atividade principal, conforme mostrado no exemplo a seguir:

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);
}

Com esse código, a NavigationUI vai navegar até o gráfico de biblioteca adequado quando o usuário clicar em um item de navegação inferior.

Geralmente, não é recomendado o módulo do app ter uma dependência forte de um destino específico incorporado de forma profunda ao gráfico de navegação dos módulos de recursos. Na maioria dos casos, convém que o módulo do app saiba apenas sobre o ponto de entrada de todos os gráficos de navegação incorporados ou incluídos. Isso também se aplica fora de módulos de recursos. Se você precisar criar um link para um destino profundo no gráfico de navegação da biblioteca, a maneira preferencial de fazer isso é usando um link direto. Links diretos também são a única maneira de uma biblioteca navegar para um destino no gráfico de navegação de outra biblioteca.

Como navegar por módulos de recursos

Durante a compilação, os módulos de recursos independentes não podem acessar uns aos outros, então não é possível usar IDs para navegar para destinos em outros módulos. Em vez disso, use um link direto para navegar diretamente até um destino associado a um link direto implícito.

Continuando com o exemplo anterior, imagine que você precisa navegar de um botão no módulo :feature:home até um destino aninhado no módulo :feature:settings. Para fazer isso, adicione um link direto para o destino no gráfico de navegação das configurações, conforme mostrado abaixo:

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

Depois, adicione o código abaixo ao onClickListener do botão no fragmento de início:

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);
    }
});

Diferentemente da navegação usando IDs de ação ou de destino, você pode navegar para qualquer URI em qualquer gráfico, mesmo entre módulos.

Ao navegar usando URI, a backstack não é redefinida. Esse comportamento é diferente de outras navegações de link direto explícito, em que a backstack é substituída durante a navegação.