L'injection de dépendances manuelle ou les localisateurs de services dans une application Android peuvent poser problème en fonction de la taille de votre projet. Vous pouvez limiter la complexité de votre projet à mesure qu'il évolue en utilisant Dagger pour gérer les dépendances.
Dagger génère automatiquement du code qui imite le code que vous auriez écrit manuellement. Comme le code est généré au moment de la compilation, il est vérifiable et plus performant que d'autres solutions basées sur la réflexion telles que Guice.
Avantages de Dagger
Dagger vous dispense d'écrire du code récurrent fastidieux et sujet aux erreurs en vous permettant de procéder comme suit :
Générer le code
AppContainer
(graphique d'application) implémenté manuellement dans la section d'injection de dépendances manuelle.Créer des fabriques pour les classes disponibles dans le graphique d'application. Cela permet de satisfaire les dépendances en interne.
Décider s'il faut réutiliser une dépendance ou créer une instance en utilisant des champs d'application.
Créer des conteneurs pour des flux spécifiques comme vous l'avez fait avec le flux de connexion dans la section précédente en utilisant les sous-composants Dagger. Cela permet d'améliorer les performances de votre application en libérant la mémoire d'objets lorsqu'ils ne sont plus nécessaires.
Dagger effectue automatiquement toutes ces opérations au moment de la compilation, à condition que vous déclariez les dépendances d'une classe et que vous précisiez comment les satisfaire à l'aide d'annotations. Dagger génère du code semblable à celui que vous auriez écrit manuellement. En interne, Dagger crée un graphique d'objets auquel il peut faire référence pour trouver le moyen de fournir l'instance d'une classe. Pour chaque classe du graphique, Dagger génère une classe de type fabrique qu'il utilise en interne pour obtenir les instances de ce type.
Au moment de la compilation, Dagger vérifie votre code et procède comme suit :
Il crée et valide les graphiques de dépendances, ce qui permet de garantir ce qui suit :
- Les dépendances de chaque objet peuvent être satisfaites. Il n'existe donc aucune exception d'exécution.
- Comme il n'existe aucun cycle de dépendance, il n'y a aucune boucle infinie.
Il génère les classes utilisées lors de l'exécution pour créer les objets réels et leurs dépendances.
Un cas d'utilisation simple dans Dagger : génération d'une fabrique
Pour vous montrer comment utiliser Dagger, nous allons créer une fabrique simple pour la classe UserRepository
illustrée dans le diagramme suivant :
Définissez UserRepository
comme suit :
Kotlin
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } ... }
Ajoutez une annotation @Inject
au constructeur UserRepository
pour que Dagger sache comment créer un UserRepository
:
Kotlin
// @Inject lets Dagger know how to create instances of this object class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; // @Inject lets Dagger know how to create instances of this object @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
Dans l'extrait de code ci-dessus, vous indiquez à Dagger :
Comment créer une instance
UserRepository
avec le constructeur annoté@Inject
.Ses dépendances :
UserLocalDataSource
etUserRemoteDataSource
.
Dagger sait maintenant comment créer une instance de UserRepository
, mais il ne sait pas comment créer ses dépendances. Si vous annotez également les autres classes, Dagger sait comment les créer :
Kotlin
// @Inject lets Dagger know how to create instances of these objects class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... }
Java
public class UserLocalDataSource { @Inject public UserLocalDataSource() { } } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } }
Composants Dagger
Dagger peut créer un graphique des dépendances de votre projet pour déterminer où il doit récupérer ces dépendances en cas de besoin.
Pour ce faire, vous devez créer une interface et l'annoter avec @Component
. Dagger crée un conteneur comme vous l'auriez fait avec l'injection de dépendances manuelle.
Dans l'interface @Component
, vous pouvez définir des fonctions qui renvoient des instances des classes dont vous avez besoin (par exemple, UserRepository
). @Component
indique à Dagger de générer un conteneur avec toutes les dépendances requises pour satisfaire les types qu'il expose. Cela s'appelle un composant Dagger. Il contient un graphique qui se compose des objets que Dagger sait fournir et de leurs dépendances respectives.
Kotlin
// @Component makes Dagger create a graph of dependencies @Component interface ApplicationGraph { // The return type of functions inside the component interface is // what can be provided from the container fun repository(): UserRepository }
Java
// @Component makes Dagger create a graph of dependencies @Component public interface ApplicationGraph { // The return type of functions inside the component interface is // what can be consumed from the graph UserRepository userRepository(); }
Lorsque vous créez le projet, Dagger génère une implémentation de l'interface ApplicationGraph
à votre place : DaggerApplicationGraph
. Grâce à son processeur d'annotations, Dagger crée un graphique de dépendances constitué des relations entre les trois classes (UserRepository
, UserLocalDatasource
et UserRemoteDataSource
) avec un seul point d'entrée : obtenir une instance UserRepository
. Vous pouvez l'utiliser comme suit :
Kotlin
// Create an instance of the application graph val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() // Grab an instance of UserRepository from the application graph val userRepository: UserRepository = applicationGraph.repository()
Java
// Create an instance of the application graph ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); // Grab an instance of UserRepository from the application graph UserRepository userRepository = applicationGraph.userRepository();
Dagger crée une instance de UserRepository
chaque fois que cela est demandé.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository != userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository != userRepository2)
Vous avez parfois besoin d'une instance unique d'une dépendance dans un conteneur. Cela peut s'avérer utile pour plusieurs raisons :
Vous souhaitez que d'autres types ayant ce type comme dépendance partagent la même instance, comme plusieurs objets
ViewModel
dans le flux de connexion utilisant les mêmesLoginUserData
.La création d'un objet est coûteuse et vous ne souhaitez pas créer une instance à chaque fois qu'il est déclaré en tant que dépendance (par exemple, un analyseur JSON).
Dans cet exemple, vous pourriez souhaiter qu'une instance unique de UserRepository
soit disponible dans le graphique afin de toujours obtenir la même instance chaque fois que vous demandez un UserRepository
. Ceci utile dans votre exemple, car dans une application réelle avec un graphique d'application plus complexe, vous pouvez avoir plusieurs objets ViewModel
qui dépendent de UserRepository
et ne pas vouloir créer des instances de UserLocalDataSource
et UserRemoteDataSource
chaque fois que UserRepository
doit être fourni.
Dans le cas d'une injection de dépendances manuelle, vous devez transmettre la même instance de UserRepository
aux constructeurs des classes ViewModel. Cependant, dans Dagger, comme vous n'écrivez pas ce code manuellement, vous devez indiquer à Dagger que vous souhaitez utiliser la même instance. Pour ce faire, utilisez des annotations de champ d'application.
Définir le champ d'application avec Dagger
Vous pouvez utiliser des annotations de champ d'application pour limiter la durée de vie d'un objet à celle de son composant. Cela signifie que la même instance d'une dépendance est utilisée chaque fois que ce type doit être fourni.
Pour obtenir une instance unique de UserRepository
lorsque vous demandez le dépôt dans ApplicationGraph
, utilisez la même annotation de champ d'application pour l'interface @Component
et UserRepository
. Vous pouvez utiliser l'annotation @Singleton
déjà fournie avec le package javax.inject
que Dagger utilise :
Kotlin
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are bound to the life of the graph and so // the same instance of that type is provided every time the type is requested. @Singleton @Component interface ApplicationGraph { fun repository(): UserRepository } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are scoped to the graph and the same // instance of that type is provided every time the type is requested. @Singleton @Component public interface ApplicationGraph { UserRepository userRepository(); } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
Vous pouvez également créer et utiliser une annotation de champ d'application personnalisée. Vous pouvez créer une annotation de champ d'application comme suit :
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
Ensuite, vous pouvez l'utiliser comme avant :
Kotlin
@MyCustomScope @Component interface ApplicationGraph { fun repository(): UserRepository } @MyCustomScope class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val service: UserService ) { ... }
Java
@MyCustomScope @Component public interface ApplicationGraph { UserRepository userRepository(); } @MyCustomScope public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
Dans les deux cas, le champ d'application de l'objet est identique à celui utilisé pour annoter l'interface @Component
. Ainsi, chaque fois que vous appelez applicationGraph.repository()
, vous obtenez la même instance de UserRepository
.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository == userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository == userRepository2)
Conclusion
Il est important de connaître les avantages de Dagger et ses principes de base avant de pouvoir l'utiliser dans des scénarios plus complexes.
Sur la page suivante, vous apprendrez à ajouter Dagger à une application Android.