Modulariser le code de navigation

Cette page est un guide pour modulariser votre code de navigation. Elle est destinée à compléter les conseils généraux sur 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 les entryProviders et le contenu navigable de chaque fonctionnalité dans le module impl associé.
  • Fournissez des 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

Clés de navigation

impl

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

Cette approche permet à une fonctionnalité de naviguer vers 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és 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 en modules distincts, créez des fonctions d'extension sur EntryProviderScope et déplacez-les dans le module impl de cette fonctionnalité. Il s'agit de constructeurs d'entrées.

L'exemple de code suivant montre un constructeur 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 entryProvider DSL 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 constructeur d'entrées est appelé directement par l'application principale à l'aide du DSL entryProvider. Si votre application comporte de nombreux écrans ou modules de fonctionnalités, ce modèle n'est peut-être pas adapté.

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

Par exemple, le code suivant utilise des multibindings Dagger, en particulier @IntoSet, pour injecter les constructeurs d'entrées dans un Set appartenant à MainActivity. Ils sont ensuite appelés de manière itérative dans entryProvider, ce qui évite d'avoir à appeler explicitement de nombreuses fonctions de constructeur 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'interface utilisateur 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 constructeur.

Si votre application est compatible avec les liens profonds et est modularisée, chaque module de fonctionnalité doit définir les DeepLinkMatcher instances pour les destinations qu'il possède.

Pour regrouper tous les correspondances de votre application, vous pouvez utiliser des multibindings d'injection de dépendances. Par exemple, chaque module de fonctionnalité peut contribuer ses correspondances à un multibinding Dagger @IntoSet :

Module de fonctionnalité

Ensuite, dans le module d'application principal, par exemple dans MainActivity, vous pouvez injecter l'ensemble des correspondances et les utiliser pour faire correspondre les requêtes entrantes :

Module d'application

Pour en savoir plus sur la définition et la gestion des liens profonds, consultez la section Compatibilité avec les liens profonds.

Ressources

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