Modulariser le code de navigation

Cette page vous explique comment modulariser votre code de navigation. Il est destiné à compléter les conseils généraux pour la modularisation des applications.

Présentation

La modularisation de votre code de navigation consiste à séparer les clés de navigation associées et le contenu qu'elles représentent en modules individuels. Cela permet de séparer clairement les responsabilités et de naviguer entre les différentes fonctionnalités de votre application.

Pour modulariser votre code de navigation, procédez comme suit :

  • Créez deux sous-modules : api et impl pour chaque fonctionnalité de votre application.
  • Placez les clés de navigation de chaque fonctionnalité dans son module api.
  • Placez entryProviders et le contenu navigable pour chaque fonctionnalité dans le module impl associé.
  • Fournissez entryProviders à vos modules d'application principaux, directement ou à l'aide de l'injection de dépendances.

Séparer les fonctionnalités en sous-modules d'API et d'implémentation

Pour chaque fonctionnalité de votre application, créez deux sous-modules nommés api et impl (abréviation de "implémentation"). Utilisez le tableau suivant pour déterminer où placer le code de navigation.

Nom du module

Contient

api

Touches de navigation

impl

Contenu de cette fonctionnalité, y compris les définitions des NavEntry et du entryProvider. Consultez également Résoudre les clés de contenu.

Cette approche permet à une fonctionnalité d'accéder à une autre en autorisant son contenu, contenu dans son module impl, à dépendre des clés de navigation d'un autre module, contenu dans le module api de ce module.

Diagramme de dépendances des modules de fonctionnalité montrant comment les modules `impl` peuvent dépendre des modules `api`.
Figure 1. Diagramme de dépendances des modules de fonctionnalité montrant comment les modules d'implémentation peuvent dépendre des modules d'API.

Séparer les entrées de navigation à l'aide de fonctions d'extension

Dans Navigation 3, le contenu navigable est défini à l'aide d'entrées de navigation. Pour séparer ces entrées dans des modules distincts, créez des fonctions d'extension sur EntryProviderScope et déplacez-les dans le module impl pour cette fonctionnalité. C'est ce que nous appelons des constructeurs d'entrées.

L'exemple de code suivant montre un générateur d'entrées qui crée deux entrées de navigation.

// 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
        }
    }
}

Appelez cette fonction à l'aide du DSL entryProvider lorsque vous définissez votre entryProvider dans le module d'application principal.

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

Utiliser l'injection de dépendances pour ajouter des entrées à l'application principale

Dans l'exemple de code précédent, chaque générateur d'entrée est appelé directement par l'application principale à l'aide du DSL entryProvider. Si votre application comporte de nombreux écrans ou modules de fonctionnalités, cela peut ne pas bien s'adapter.

Pour résoudre ce problème, faites en sorte que chaque module de fonctionnalité contribue à ses constructeurs d'entrée dans l'activité de l'application à l'aide de l'injection de dépendances.

Par exemple, le code suivant utilise des multibindings Dagger, plus précisément @IntoSet, pour injecter les builders d'entrée dans un Set appartenant à MainActivity. Elles sont ensuite appelées de manière itérative dans entryProvider, ce qui évite d'avoir à appeler explicitement de nombreuses fonctions de création d'entrées.

Module de fonctionnalité

// 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()
    }
}

Module d'application

// 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() }
                },
                // ...
            )
        }
    }
}

Si vos entrées de navigation doivent naviguer (par exemple, si elles contiennent des éléments d'UI qui permettent d'accéder à de nouveaux écrans), injectez un objet capable de modifier l'état de navigation de l'application dans chaque fonction de création.

Ressources

Pour obtenir des exemples de code montrant comment modulariser le code Navigation 3, consultez les ressources suivantes :