Modularizar o código de navegação

Esta página é um guia para modularizar seu código de navegação. Ele complementa a orientação geral para 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 seu 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 o conteúdo navegável de cada recurso no módulo impl associado.
  • Forneça entryProviders aos módulos principais do app, diretamente ou usando 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

teclas de navegação

impl

Conteúdo desse recurso, incluindo definições de 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 desse módulo.

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 recurso 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 diferentes, crie funções de extensão em EntryProviderScope e mova-as para o módulo impl desse recurso. Eles são conhecidos como criadores de entradas.

O exemplo de código a seguir mostra um criador de entradas 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 DSL entryProvider ao definir seu 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 entradas é chamado diretamente pelo app principal usando a DSL entryProvider. Se o app tiver muitas telas ou módulos de recursos, isso pode não ser escalonável.

Para resolver isso, faça com que cada módulo de recurso contribua com os criadores de entradas na 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 entradas em um Set pertencente a MainActivity. Elas são chamadas de forma iterativa dentro de entryProvider, eliminando a necessidade de chamar explicitamente várias funções de criador de entradas.

Módulo do 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 de 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, se contiverem elementos da interface que navegam para novas telas), injete um objeto capaz de modificar o estado de navegação do app em cada função de builder.

Recursos

Para exemplos de código que mostram como modularizar o código do Navigation 3, consulte: