Le temps est venu. Vous avez finalement décidé de diviser votre module Gradle monolithique en modules plus petits. Mais avez-vous également envisagé de diviser votre tout-puissant Dague
AppComponent? Tout fonctionne, alors pourquoi s'embêter, non?
Remarque : Dans l'article, les références aux modules font référence aux modules Gradle et non aux modules Dagger.
Imaginez que vous avez 3 modules
:app,
:featureet
:librarycomme dans l'image ci-dessous.
Votre
:appmodule dépend du
:featuremodule de la manière habituelle
implementation(project(":feature"))
implementation(some.library:library:1.0.0)
Vous avez choisi le
:librarycar il contient des éléments géniaux que
LibraryManagervous souhaitez utiliser dans votre base de code. Soudainement, votre voix intérieure vous a rappelé le principe d'inversion de dépendance , vous l'avez donc encapsulé
LibraryManagerdans votre propre classe
FeatureRepository. Juste après avoir arrêté d'écrire, la voix intérieure a chuchoté à nouveau. «Cette fois, vérifiez l' inversion de contrôle », dit-il. Et vous avez donc ajouté
@Injectau constructeur du
FeatureRepository.
Puisqu'il n'y avait plus de chuchotements, vous avez compilé le projet 🥁… et il a échoué.
LibraryManager cannot be provided without an @Inject constructor or an @Provides-annotated method.
Vous devez créer Dagger
@Moduledans votre
:featuremodule Gradle dont
@Providesle fichier
LibraryManager.
Maintenant, ça devrait aller, non? Vous n'en avez pas
importde la bibliothèque dans le
:appmodule - seulement votre fichier
FeatureRepository.
Vous compilez à nouveau… et cela échoue, encore une fois.
DaggerAppComponent.java:26: error: cannot access LibraryManager
La même chose se produirait si vous vouliez accéder à la classe manuellement à partir de votre
:appmodule. Mais dans ce cas, l'inspection des erreurs IDE vous donnerait un indice.
L'image ci-dessous explique ce qui se passe graphiquement.
Que peux-tu y faire?
La solution la plus simple est de passer
implementationau
apifichier Gradle et c'est fait 🎤 ⤵️. Mais s'il vous plaît, ne le faites pas 🙏. Il rompt l'encapsulation - rendant
FeatureManagervisible à tous vos modules! De plus, chaque fois que l'
:libraryABI change, Gradle recompilera chaque module en fonction du
:featuremodule!
La solution appropriée est un peu plus difficile qu'une solution en une ligne. Je vais vous montrer comment faire - mais d'abord, j'aimerais jeter plus de bois dans le feu en décrivant une situation similaire: encapsuler votre propre code.
Imaginez le même projet avec les modules
:appet
:feature(nous l'oublierons
:librarypour cet exemple). Vous voulez que votre
FeatureRepositoryêtre l'
publicinterface et la mise en œuvre
FeatureRepositoryImpld'être
internal, de sorte que rien que le
:featuremodule peut accéder à ses tripes.
L'image et le code ci-dessous représentent la situation:
Dans ce cas, vous n'avez pas à utiliser explicitement
@Providespour votre
FeatureRepository, mais vous devez indiquer à Dagger la relation entre l'implémentation et son interface avec
@Binds(
@Providescela fonctionnerait également, mais c'est moins performant).
Comme vous l'avez deviné, vous serez confronté au même problème qu'avant. Mais cette fois, même l'inspection des erreurs IDE vous indique que vous ne pouvez pas faire cela:
`public` function exposes its `internal` parameter type FeatureRepositoryImpl
Pour encapsuler les dépendances dans les modules Gradle, vous devez créer une dague distincte
@Componentdans chaque module. Vous ne pouvez pas créer
@Subcomponentpour cela, car le sous-composant génère du code dans son composant parent (et donc toujours dans le
:app).
Techniquement, l'encapsulation des
internalimplémentations fonctionnerait avec
@Subcomponent. Java n'a pas de
internalmodificateur de visibilité et donc les classes compilées à partir de Kotlin le sont
public . Mais si vous souhaitez encapsuler une dépendance tierce, le problème sera le même: il ne se compilera pas.
Pour faire d'une pierre deux coups, nous devons nous en tenir aux dépendances des composants.
Tout d'abord, nous devons créer
FeatureComponentet spécifier explicitement ce qu'il peut fournir en dehors du composant - c'est le type de modificateur
public/
privatevisibilité de Dagger :
Ensuite, ajoutez le composant à
dependenciesrépertorier dans
AppComponent:
Et enfin, instanciez les deux composants dans votre méthode d'usine
Applicationet passez
FeatureComponentà
AppComponent.
Vous pouvez désormais profiter des dépendances encapsulées 🎉. Vous n'avez plus à vous soucier que quelqu'un utilise une implémentation au lieu d'une abstraction. Vous n'avez pas non plus à vous soucier de polluer votre base de code avec du code de bibliothèque.
Cette configuration permet aux deux exemples du début de l'article de fonctionner.
Il y a cependant un hic (il y en a toujours, non? 😅). Si vous voulez avoir votre
FeatureComponentportée (par exemple
@FeatureScope), la compilation échouera à nouveau.
AppComponent.java:7: error: This @Singleton component cannot depend on scoped components: @FeatureScope FeatureComponent
Le composant Dagger est une interface annotée simple (ou une classe abstraite) avec une implémentation générée. Nous pouvons tromper le compilateur Dagger pour qu'il saute la vérification de la portée en passant la dépendance à un «contrat» au lieu du composant réel.
Cela fonctionne comme ceci:
AppComponentdépend de
FeatureComponentContract, qui est implémenté par le
FeatureComponent. De cette façon,
AppComponentne dépend pas directement du fichier
FeatureComponent. Étant donné que le contrat n'est qu'une simple interface, Dagger ne se plaindra pas des portées. En même temps, il honorera les annotations des composants et générera leurs implémentations.
Cependant, nous devrons déplacer la
featureRepository: FeatureRepositorydéfinition vers l'interface du contrat. De cette façon,
AppComponentsait qu'il peut obtenir le
featureRepositorycontrat et ne se soucie pas de ce qui met en œuvre le contrat.
Il n'y aura aucun changement dans votre
Applicationclasse, car tout accepte la même interface.
Visuellement, cela ressemble à ceci:
La représentation du code de
FeatureComponentavec
FeatureComponentContractressemble à ceci:
et enfin
ApplicationComponent,:
De cette façon, vous pouvez avoir un composant Dagger dans chaque module Gradle et encapsuler correctement votre code. Chacun de vos composants peut contenir des «singletons locaux». Votre
FeatureComponentpeut contenir des objets annotés avec
@FeatureScope, et
AppComponentpeut contenir
@Singleton.
Il y a une astuce pour ceux qui viennent de commencer à modulariser leurs projets et à utiliser
@Singletonl'ensemble du code. Vous pouvez faire
FeatureComponentannoter avec
@Singletonau lieu d'une étendue personnalisée. Attention cependant! Cela peut entraîner le fait que votre application contienne plusieurs instances de la même classe annotées avec
@Singleton, car chaque classe étendue est contenue dans son composant . Si vous avez un
@Modulequi
@Providesle même
@Singletonobjet à l' intérieur des composants multiples, il se tiendra comme un singleton dans chacun de ces composants.
De nos jours, de nombreuses applications ont plusieurs modules Gradle - mais s'en tiennent à un composant Dagger. Si vous souhaitez plus de contrôle sur la visibilité de vos dépendances, vous devez envisager de verrouiller le composant Dagger dans chaque module Gradle.
PS: Cette configuration est également idéale pour les modules de fonctionnalités dynamiques, mais vous en saurez plus la prochaine fois 🤫.
Merci Iveta Jurčíková et Marek Abaffy d'avoir relu l'article.
Bienvenue sur Android Weekly - une newsletter sur les nouveaux développements et mises à jour de l'univers Android organisée par l'équipe Canopas, livrée tous les lundis. Articles Gérer onNewIntent dans Jetpack Compose Tout le monde sait que nous pouvons remplacer onNewIntent dans notre activité, mais cela crée un autre problème.
Introduction Bonjour à tous, dans ce court article, je vais vous montrer comment afficher du texte HTML dans Android. Au moment de la rédaction de cet article, le Text composable de Jetpack compose ne nous permet pas de le faire.
À l'intérieur du nuage de données moderne et quelle est la prochaine étape. Problèmes d'îlots de données et top 8 des tendances Prelude Data Cloud est un domaine immense.
Dans le domaine de la technologie, les entreprises risquent constamment d'être la proie des activités malveillantes des cybercriminels. L'une de ces tactiques qui est devenue d'une fréquence alarmante est celle du credential stuffing.
Introduction à l'intelligence artificielle L'intelligence artificielle (IA) est une technologie en développement rapide qui a le potentiel de révolutionner les industries, de changer notre façon de vivre et de travailler et d'apporter de nombreux avantages à la société. Cependant, comme pour toute nouvelle technologie, des considérations éthiques doivent également être prises en compte.
Introduction à l'intelligence artificielle L'intelligence artificielle (IA) est une technologie en développement rapide qui a le potentiel de révolutionner les industries, de changer notre façon de vivre et de travailler et d'apporter de nombreux avantages à la société. Cependant, comme pour toute nouvelle technologie, des considérations éthiques doivent également être prises en compte.