Modularizar o código de navegação

Esta página é um guia para modularizar o código de navegação. Ela complementa as orientações gerais para a modularização de apps.

Visão geral

A modularização do código de navegação é o processo de separação de chaves de navegação relacionadas e do conteúdo que elas representam em módulos individuais. Isso oferece uma separação clara de responsabilidades e permite navegar entre diferentes recursos no app.

Para modularizar o código de navegação, faça o seguinte:

  • Crie dois submódulos: api e impl para cada recurso no app.
  • Coloque as chaves de navegação de cada recurso no módulo api.
  • Coloque entryProviders e conteúdo navegável para cada recurso no módulo impl associado.
  • Forneça entryProviders aos módulos principais do app, diretamente ou usando a injeção de dependência.

Separar recursos em submódulos de API e implementação

Para cada recurso no app, crie dois submódulos chamados api e impl (abreviação de "implementação"). Use a tabela a seguir para decidir onde colocar o código de navegação.

Nome do módulo

Contém

api

chaves de navegação

impl

Conteúdo desse recurso, incluindo definições para NavEntrys e o entryProvider. Consulte também Resolver chaves para conteúdo.

Essa abordagem permite que um recurso navegue para outro, permitindo que o conteúdo dele, contido no módulo impl, dependa das chaves de navegação de outro módulo, contido no módulo api.

Diagrama de dependência do módulo de recurso mostrando como os módulos "impl" podem depender dos módulos "api".
Figura 1. Diagrama de dependência do módulo de recursos mostrando como os módulos de implementação podem depender de módulos de API.

Separar entradas de navegação usando funções de extensão

Na Navegação 3, o conteúdo navegável é definido usando entradas de navegação. Para separar essas entradas em módulos separados, crie funções de extensão em EntryProviderScope e mova-as para o impl módulo desse recurso. Elas são conhecidas como criadores de entrada.

O exemplo de código a seguir mostra um criador de entrada que cria duas entradas de navegação.

// import androidx.navigation3.runtime.EntryProviderScope
// import androidx.navigation3.runtime.NavKey

fun EntryProviderScope<NavKey>.featureAEntryBuilder() {
    entry<KeyA> {
        ContentRed("Screen A") {
            // Content for screen A
        }
    }
    entry<KeyA2> {
        ContentGreen("Screen A2") {
            // Content for screen A2
        }
    }
}

Chame essa função usando a entryProvider DSL ao definir o entryProvider no módulo principal do app.

// import androidx.navigation3.runtime.entryProvider
// import androidx.navigation3.ui.NavDisplay
NavDisplay(
    entryProvider = entryProvider {
        featureAEntryBuilder()
    },
    // ...
)

Usar a injeção de dependência para adicionar entradas ao app principal

No exemplo de código anterior, cada criador de entrada é chamado diretamente pelo app principal usando a DSL entryProvider. Se o app tiver muitas telas ou módulos de recursos, esse padrão poderá não ser escalonado bem.

Para resolver isso, faça com que cada módulo de recursos contribua com os criadores de entrada para a atividade do app usando a injeção de dependência.

Por exemplo, o código a seguir usa multibindings do Dagger, especificamente @IntoSet, para injetar os criadores de entrada em um Set pertencente a MainActivity. Eles são chamados de forma iterativa dentro de entryProvider, negando a necessidade de chamar explicitamente várias funções de criador de entrada.

Módulo de recurso

// import dagger.Module
// import dagger.Provides
// import dagger.hilt.InstallIn
// import dagger.hilt.android.components.ActivityRetainedComponent
// import dagger.multibindings.IntoSet

@Module
@InstallIn(ActivityRetainedComponent::class)
object FeatureAModule {

    @IntoSet
    @Provides
    fun provideFeatureAEntryBuilder() : EntryProviderScope<NavKey>.() -> Unit = {
        featureAEntryBuilder()
    }
}

Módulo do app

// import android.os.Bundle
// import androidx.activity.ComponentActivity
// import androidx.activity.compose.setContent
// import androidx.navigation3.runtime.EntryProviderScope
// import androidx.navigation3.runtime.NavKey
// import androidx.navigation3.runtime.entryProvider
// import androidx.navigation3.ui.NavDisplay
// import javax.inject.Inject

class MainActivity : ComponentActivity() {

    @Inject
    lateinit var entryBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NavDisplay(
                entryProvider = entryProvider {
                    entryBuilders.forEach { builder -> this.builder() }
                },
                // ...
            )
        }
    }
}

Se as entradas de navegação precisarem navegar (por exemplo, elas contêm elementos de IU que navegam para novas telas), injete um objeto capaz de modificar o estado de navegação do app em cada função do criador.

Se o app oferece suporte a links diretos e é modularizado, cada módulo de recursos precisa definir as instâncias DeepLinkMatcher para os destinos que ele possui.

Para reunir todos os correspondentes do app, use multibindings de injeção de dependência. Por exemplo, cada módulo de recursos pode contribuir com os correspondentes para um multibinding @IntoSet do Dagger:

Módulo de recurso

Em seguida, no módulo principal do app, como no MainActivity, você pode injetar o conjunto de correspondentes e usá-los para corresponder às solicitações recebidas:

Módulo do app

Para mais informações sobre como definir e processar links diretos, consulte Oferecer suporte a links diretos.

Recursos

Para exemplos de código que mostram como modularizar o código da Navegação 3, consulte: