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 :
apietimplpour chaque fonctionnalité de votre application. - Placez les clés de navigation de chaque fonctionnalité dans son module
api. - Placez les
entryProviderset le contenu navigable de chaque fonctionnalité dans le moduleimplassocié. - 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 |
|
|
|
Contenu de cette fonctionnalité, y compris les définitions des
|
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.
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.
Modulariser les correspondances de liens profonds
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é
@Module @InstallIn(ActivityRetainedComponent::class) object FeatureADeepLinkModule { @IntoSet @Provides fun provideUserMatcher(): DeepLinkMatcher<*, *> { return UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) } }
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
@AndroidEntryPoint class MainActivityWithDI : ComponentActivity() { @Inject lateinit var deepLinkMatchers: Set<@JvmSuppressWildcards DeepLinkMatcher<*, *>> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val request = DeepLinkRequest(intent = intent) val matchResult = deepLinkMatchers .mapNotNull { it.match(request) } .maxOrNull() val backStack = when (matchResult) { null -> listOf(HomeKey) is BackStackMatchResult<*, *> -> { @Suppress("UNCHECKED_CAST") matchResult.backStack as List<NavKey> } else -> listOf(matchResult.key) } } }
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 :
- Recettes de code d'architecture de Navigation 3
- Parcours de formation sur la modularisation de l'application Now in Android
- Androidify