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 singular, 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 de biblioteca separados.

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

arquitetura do app de exemplo
o destino inicial do app de exemplo
Figura 1. Arquitetura do app e destino inicial do app de exemplo.

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

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

O papel do módulo app

O módulo 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 app, você pode referenciar os gráficos da biblioteca usando <include>. Embora o uso de <include> seja funcionalmente o mesmo que usar um gráfico aninhado, <include> é compatível com gráficos de outros módulos do projeto ou de projetos de biblioteca, como mostrado no exemplo a seguir:

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

Quando uma biblioteca for incluída no gráfico de navegação de nível superior, 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/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>

Quando vários módulos de biblioteca precisam referenciar um conjunto comum de destinos, como um gráfico de login, você não poded incluir esses destinos comuns no gráfico de navegação de cada módulo de biblioteca. Em vez disso, adicione esses destinos comuns ao gráfico de navegação do módulo app. Cada módulo de biblioteca 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 superior do app, a gaveta de navegação e a navegação inferior. Se os destinos de nível superior do app forem compostos por elementos da IU fornecidos por módulos de biblioteca, o módulo app será um lugar natural para colocar os elementos da IU e a navegação de nível superior. Como o módulo do app depende dos módulos da biblioteca colaborativa, todos os destinos são acessíveis no código definido no módulo do app. Isso significa que você pode usar NavigationUI para associar destinos a itens de menu se o ID do item corresponder 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/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 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, NavigationUI navegará até o gráfico de biblioteca adequado quando o usuário clicar em um item de navegação inferior.

Lembre-se de que, geralmente, não é recomendado o módulo do app ter uma dependência forte em um destino específico incorporado profundamente ao gráfico de navegação dos módulos da biblioteca. Na maioria dos casos, convém que o módulo do app saiba somente sobre o ponto de entrada de qualquer gráfico de navegação incorporado ou incluído. Isso também se aplica fora de módulos de biblioteca. 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 biblioteca

Durante a compilação, os módulos de bibliotecas independentes não podem ver 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 de lista até um destino aninhado no módulo de configurações. 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>

Em seguida, adicione o código a seguir ao onClickListener do botão no fragmento da 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);
    }
});

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.