paint-brush
Injection de dépendances avec Dagger 2 : qu'est-ce que c'est, concepts clés et plus encorepar@dilip2882
Nouvelle histoire

Injection de dépendances avec Dagger 2 : qu'est-ce que c'est, concepts clés et plus encore

par Dilip Patel21m2024/08/28
Read on Terminal Reader

Trop long; Pour lire

L'injection de dépendances (DI) est un modèle de conception utilisé pour implémenter l'inversion de contrôle (IoC). Il s'agit d'une technique dans laquelle la responsabilité de la création d'objets est transférée à d'autres parties du code. Cela favorise un couplage lâche, rendant le code plus modulaire et plus facile à gérer.
featured image - Injection de dépendances avec Dagger 2 : qu'est-ce que c'est, concepts clés et plus encore
Dilip Patel HackerNoon profile picture
0-item


Introduction à l'injection de dépendances

L'injection de dépendances (DI) est un modèle de conception utilisé pour implémenter l'inversion de contrôle (IoC) où le contrôle de la création et de la gestion des dépendances est transféré de l'application à une entité externe. Cela permet de créer un code plus modulaire, testable et maintenable. Il s'agit d'une technique où la responsabilité de la création d'objets est transférée à d'autres parties du code. Cela favorise un couplage lâche, rendant le code plus modulaire et plus facile à gérer.

Les classes ont souvent besoin de références à d'autres classes pour fonctionner correctement. Par exemple, considérons une classe Library qui nécessite une classe Book . Ces classes nécessaires sont appelées dépendances. La classe Library a besoin d'une instance de la classe Book pour fonctionner.

hyperskill.org

Il existe trois manières principales pour une classe d'obtenir les objets dont elle a besoin :

  1. Auto-construction : La classe crée et initialise ses propres dépendances. Par exemple, la classe Library créerait et initialiserait sa propre instance de la classe Book .
  2. Récupération externe : la classe récupère les dépendances à partir d'une source externe. Certaines API Android, telles que les getters Context et getSystemService() , fonctionnent de cette manière.
  3. Injection de dépendances : les dépendances sont fournies à la classe, soit lors de sa construction, soit via des méthodes qui les requièrent. Par exemple, le constructeur Library recevrait une instance Book en tant que paramètre.

La troisième option est l'injection de dépendances ! Avec l'injection de dépendances, vous fournissez les dépendances d'une classe plutôt que de laisser l'instance de classe les obtenir elle-même.

Exemple sans injection de dépendance

Sans DI, une Library qui crée sa propre dépendance Book pourrait ressembler à ceci :

 class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }

Il ne s'agit pas d'un exemple de DI car la classe Library construit son propre Book . Cela peut être problématique car :

  • Couplage étroit : Library et Book sont étroitement couplés. Une instance de Library utilise un type de Book , ce qui rend difficile l'utilisation de sous-classes ou d'implémentations alternatives.
  • Difficultés de test : La dépendance stricte envers Book rend les tests plus difficiles. Library utilise une instance réelle de Book , ce qui empêche l'utilisation de doublons de test pour modifier Book pour différents cas de test.

Exemple avec injection de dépendance

Avec DI, au lieu que chaque instance de Library construise son propre objet Book , elle reçoit un objet Book comme paramètre dans son constructeur :

 class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }

La fonction principale utilise Library . Étant donné que Library dépend de Book , l'application crée une instance de Book et l'utilise ensuite pour construire une instance de Library . Les avantages de cette approche basée sur l'injection de dépendances sont les suivants :

  • Réutilisabilité de Library : vous pouvez transmettre différentes implémentations de Book à Library . Par exemple, vous pouvez définir une nouvelle sous-classe de Book appelée EBook que vous souhaitez que Library utilise. Avec DI, vous transmettez simplement une instance de EBook à Library , et cela fonctionne sans aucune autre modification.
  • Test facile de Library : vous pouvez passer des tests doublons pour tester différents scénarios.

Un autre exemple de DI

Considérez un scénario dans lequel une classe NotificationService s'appuie sur une classe Notification . Sans DI, NotificationService crée directement une instance de Notification , ce qui rend difficile l'utilisation de différents types de notifications ou le test du service avec diverses implémentations de notifications.

Pour illustrer DI, refactorisons cet exemple :

 interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }

Désormais, NotificationService dépend de l'interface Notification plutôt que d'une classe spécifique. Cela permet d'utiliser différentes implémentations de Notification de manière interchangeable. Vous pouvez définir l'implémentation que vous souhaitez utiliser via la méthode sendNotification :

 NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());

Méthodes d'injection de dépendances dans Android

Il existe trois principaux types de DI :

  1. Injection de méthode (interface) : les dépendances sont transmises via des méthodes auxquelles la classe peut accéder via une interface ou une autre classe. L'exemple précédent illustre l'injection de méthode.
  2. Injection de constructeur : les dépendances sont transmises à la classe via son constructeur.
 class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }

3. Injection de champ (ou injection de setter) : certaines classes du framework Android, telles que les activités et les fragments, sont instanciées par le système, de sorte que l'injection de constructeur n'est pas possible. Avec l'injection de champ, les dépendances sont instanciées après la création de la classe.

 class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }

4. Injection de méthode : les dépendances sont fournies via des méthodes, souvent à l'aide de l'annotation @Inject .

Avantages de l'injection de dépendances

  • Les classes deviennent plus réutilisables et moins dépendantes d'implémentations spécifiques. Cela est dû à l'inversion de contrôle, où les classes ne gèrent plus leurs dépendances mais fonctionnent avec n'importe quelle configuration fournie.
  • Les dépendances font partie de la surface de l'API et peuvent être vérifiées lors de la création de l'objet ou au moment de la compilation, ce qui facilite le refactoring.
  • Étant donné qu'une classe ne gère pas ses dépendances, différentes implémentations peuvent être transmises lors des tests pour couvrir différents scénarios.

Injection de dépendances automatisée

Dans l'exemple précédent, vous avez créé, fourni et géré manuellement les dépendances de différentes classes sans utiliser de bibliothèque. Cette approche est connue sous le nom d'injection de dépendances manuelle. Bien qu'elle fonctionne pour les cas simples, elle devient fastidieuse à mesure que le nombre de dépendances et de classes augmente. L'injection de dépendances manuelle présente plusieurs inconvénients :

  • Code standard : pour les applications volumineuses, la gestion de toutes les dépendances et leur connexion correcte peuvent entraîner une grande quantité de code répétitif. Dans une architecture multicouche, la création d'un objet pour une couche supérieure nécessite de fournir toutes les dépendances pour les couches inférieures. Par exemple, pour construire un ordinateur, vous avez besoin d'un processeur, d'une carte mère, de RAM et d'autres composants ; et un processeur peut avoir besoin de transistors et de condensateurs.
  • Gestion des dépendances complexes : lorsque vous ne pouvez pas créer de dépendances à l'avance (par exemple avec des initialisations paresseuses ou la définition d'objets dans des flux spécifiques de votre application), vous devez écrire et maintenir un conteneur personnalisé (ou un graphique de dépendances) pour gérer la durée de vie de vos dépendances en mémoire.

Les bibliothèques peuvent automatiser ce processus en créant et en fournissant des dépendances pour vous. Ces bibliothèques se répartissent en deux catégories :

  1. Solutions basées sur la réflexion : elles connectent les dépendances au moment de l’exécution.
  2. Solutions statiques : elles génèrent du code pour connecter les dépendances au moment de la compilation.

Dagger est une bibliothèque d'injection de dépendances populaire pour Java, Kotlin et Android, gérée par Google. Dagger simplifie l'injection de dépendances dans votre application en créant et en gérant le graphique de dépendances pour vous. Il fournit des dépendances entièrement statiques au moment de la compilation, résolvant de nombreux problèmes de développement et de performances associés aux solutions basées sur la réflexion comme Guice.

Solutions basées sur la réflexion

Ces frameworks connectent les dépendances au moment de l'exécution :

  1. Toothpick : un framework d'exécution DI qui utilise la réflexion pour connecter les dépendances. Il est conçu pour être léger et rapide, ce qui le rend adapté aux applications Android.

Solutions statiques

Ces frameworks génèrent du code pour connecter les dépendances au moment de la compilation :

  1. Hilt : Construit sur la base de Dagger, Hilt fournit un moyen standard d'intégrer l'injection de dépendances Dagger dans une application Android. Il simplifie la configuration et l'utilisation de Dagger en fournissant des composants et des portées prédéfinis .
  2. Koin : un framework DI léger et simple pour Kotlin. Koin utilise un DSL pour définir les dépendances et est facile à configurer et à utiliser.
  3. Kodein : un framework DI basé sur Kotlin, facile à utiliser et à comprendre. Il fournit une API simple et flexible pour la gestion des dépendances.

Alternatives à l'injection de dépendances

Une alternative à l'injection de dépendances est le modèle de localisateur de services. Ce modèle de conception permet également de découpler les classes de leurs dépendances concrètes. Vous créez une classe appelée localisateur de services qui crée et stocke les dépendances, en les fournissant à la demande.

 object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }

Le modèle de localisateur de services diffère de l'injection de dépendances dans la manière dont les dépendances sont consommées. Avec le modèle de localisateur de services, les classes demandent les dépendances dont elles ont besoin ; avec l'injection de dépendances, l'application fournit de manière proactive les objets requis.

Qu'est-ce que Dagger 2 ?

Dagger 2 est un framework DI populaire pour Android. Il utilise la génération de code au moment de la compilation et est connu pour ses hautes performances. Dagger 2 simplifie le processus d'injection de dépendances en générant le code nécessaire pour gérer les dépendances, en réduisant le code standard et en améliorant l'efficacité.

Dagger 2 est une bibliothèque basée sur des annotations pour l'injection de dépendances dans Android. Voici les principales annotations et leurs objectifs :

  • @Module : utilisé pour définir des classes qui fournissent des dépendances. Par exemple, un module peut fournir un ApiClient pour Retrofit.
  • @Provides : annote les méthodes d'un module pour spécifier comment créer et renvoyer des dépendances.
  • @Inject : utilisé pour demander des dépendances. Peut être appliqué aux champs, aux constructeurs et aux méthodes.
  • @Component : une interface qui relie @Module et @Inject . Elle contient tous les modules et fournit le générateur pour l'application.
  • @Singleton : garantit qu'une seule instance d'une dépendance est créée.
  • @Binds : utilisé dans les classes abstraites pour fournir des dépendances, similaire à @Provides mais plus concis.

Composants de la dague

Dagger peut générer un graphique de dépendances pour votre projet, lui permettant de déterminer où obtenir les dépendances en cas de besoin. Pour cela, vous devez créer une interface et l'annoter avec @Component .

Dans l'interface @Component , vous définissez des méthodes qui renvoient des instances des classes dont vous avez besoin (par exemple, BookRepository ). L'annotation @Component demande à Dagger de générer un conteneur avec toutes les dépendances requises pour satisfaire les types qu'il expose. Ce conteneur est connu sous le nom de composant Dagger et il contient un graphique d'objets que Dagger sait fournir avec leurs dépendances.


Exemple

Considérons un exemple impliquant un LibraryRepository :

  1. Annoter le constructeur : ajoutez une annotation @Inject au constructeur LibraryRepository afin que Dagger sache comment créer une instance de LibraryRepository .
 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }

2. Annoter les dépendances : De même, annotez les constructeurs des dépendances ( LocalLibraryDataSource et RemoteLibraryDataSource ) afin que Dagger sache comment les créer.

 public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }

3. Définir le composant : Créez une interface annotée avec @Component pour définir le graphe de dépendances.

 @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }

Lorsque vous générez le projet, Dagger génère pour vous une implémentation de l'interface ApplicationComponent , généralement nommée DaggerApplicationComponent .

Usage

Vous pouvez maintenant utiliser le composant généré pour obtenir des instances de vos classes avec leurs dépendances injectées automatiquement :

 public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Dans votre activité ou fragment, vous pouvez récupérer l'instance LibraryRepository :

 public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }

Concepts clés de Dagger 2

1. Modules
∘ Concepts clés des modules
∘ Inclure des modules dans les composants
2. Portées
3. Composants
4. Dépendances des composants
5. Liaisons d'exécution

1. Modules

Les modules de Dagger 2 sont des classes annotées avec @Module qui fournissent des dépendances aux composants. Ils contiennent des méthodes annotées avec @Provides ou @Binds pour spécifier comment créer et fournir des dépendances. Les modules sont essentiels pour organiser et gérer la création des objets dont votre application a besoin.

Concepts clés des modules

  1. @Module Annotation : cette annotation permet de définir une classe comme un module Dagger. Une classe de module contient des méthodes qui fournissent des dépendances.
  2. @Provides Annotation : cette annotation est utilisée sur les méthodes d'un module pour indiquer que la méthode fournit une certaine dépendance. Ces méthodes sont responsables de la création et du renvoi des instances des dépendances.
  3. Annotation @Binds : cette annotation est utilisée dans les classes abstraites pour lier une implémentation à une interface. Elle est plus concise que @Provides et est utilisée lorsque le module est une classe abstraite.

Exemple de module

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }

Dans cet exemple, NetworkModule est une classe annotée avec @Module . Elle contient deux méthodes annotées avec @Provides qui créent et renvoient des instances de Retrofit et OkHttpClient .

Utilisation de @Binds

Lorsque vous disposez d'une interface et de son implémentation, vous pouvez utiliser @Binds pour lier l'implémentation à l'interface. C'est plus concis que d'utiliser @Provides .

 public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }

Dans cet exemple, ApiModule est une classe abstraite annotée avec @Module . La méthode bindApiService est annotée avec @Binds pour lier ApiServiceImpl à ApiService .

Les modules peuvent être organisés en fonction des fonctionnalités qu'ils fournissent. Par exemple, vous pouvez disposer de modules distincts pour les opérations réseau, les opérations de base de données et les dépendances liées à l'interface utilisateur.

Exemple:

  • NetworkModule : fournit des dépendances liées au réseau telles que Retrofit et OkHttpClient .
  • DatabaseModule : fournit des dépendances liées à la base de données comme RoomDatabase .
  • UIModule : fournit des dépendances liées à l'interface utilisateur telles que ViewModel et Presenter .

Inclure des modules dans les composants

Les modules sont inclus dans les composants pour fournir des dépendances aux classes qui en ont besoin. Voici comment vous pouvez le configurer :

ApplicationComponent.java :

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Dans cet exemple, ApplicationComponent inclut NetworkModule et DatabaseModule pour fournir des dépendances à l'application.

2. Portées

Les portées dans Dagger 2 sont des annotations qui définissent le cycle de vie des dépendances. Elles garantissent qu'une seule instance d'une dépendance est créée et partagée dans une portée spécifiée. Cela permet de gérer efficacement la mémoire et de garantir que les dépendances sont réutilisées le cas échéant.

  • Portée Singleton : garantit une instance unique d'une dépendance tout au long du cycle de vie de l'application.
  • Portée de l'activité : garantit une instance unique d'une dépendance dans le cycle de vie d'une activité.
  • Portée du fragment : garantit une instance unique d’une dépendance dans le cycle de vie d’un fragment.

1. Portée Singleton

Définition : La portée @Singleton garantit qu'une seule instance d'une dépendance est créée et partagée tout au long du cycle de vie de l'application.

Cette portée est généralement utilisée pour les dépendances qui doivent être partagées dans l'ensemble de l'application, telles que les clients réseau, les instances de base de données ou les préférences partagées.

Exemple:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Dans cet exemple, l'annotation @Singleton garantit que les instances Retrofit et Database fournies par NetworkModule et DatabaseModule sont des singletons et partagées dans l'ensemble de l'application.

2. Champ d'activité

Définition : @ActivityScope (une portée personnalisée) garantit qu'une seule instance d'une dépendance est créée et partagée au cours du cycle de vie d'une activité.

Cette portée est utile pour les dépendances spécifiques à une activité et doivent être recréées à chaque recréation de l'activité, comme les présentateurs ou les modèles de vue.

Exemple :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Dans cet exemple, l'annotation @ActivityScope garantit que les dépendances fournies par ActivityModule sont limitées au cycle de vie de l'activité.

3. Portée du fragment

Définition : @FragmentScope (une autre portée personnalisée) garantit qu'une seule instance d'une dépendance est créée et partagée dans le cycle de vie d'un fragment.

Cas d'utilisation : cette portée est utile pour les dépendances spécifiques à un fragment et doivent être recréées à chaque recréation du fragment, comme les présentateurs ou les modèles de vue spécifiques à un fragment.

Exemple :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }

Dans cet exemple, l'annotation @FragmentScope garantit que les dépendances fournies par FragmentModule sont limitées au cycle de vie du fragment.

3. Composants

Les dépendances entre composants permettent à un composant de dépendre d'un autre, ce qui permet la réutilisation des dépendances. Il existe deux principaux types de dépendances entre composants :

  • Composant d'application : fournit les dépendances nécessaires à l'ensemble de l'application.
  • Composant d'activité : fournit les dépendances nécessaires à une activité spécifique.

1. Composant d'application

Définition : Le composant d'application fournit des dépendances nécessaires à l'ensemble de l'application. Il est généralement limité à @Singleton pour garantir que les dépendances sont partagées dans toute l'application.

Ce composant est utilisé pour les dépendances qui doivent être disponibles à l'échelle mondiale, telles que les clients réseau, les instances de base de données ou les préférences partagées.

Exemple :

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Dans cet exemple, ApplicationComponent est responsable de la fourniture des instances Retrofit et Database , qui sont partagées dans l'ensemble de l'application.

2. Composante d'activité

Définition : Le composant Activity fournit les dépendances nécessaires à une activité spécifique. Il est généralement défini avec une portée personnalisée, telle que @ActivityScope , pour garantir que les dépendances sont recréées à chaque fois que l'activité est recréée.

Ce composant est utilisé pour les dépendances spécifiques à une activité, telles que les présentateurs ou les modèles de vue.

Exemple :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Dans cet exemple, ActivityComponent dépend d' ApplicationComponent et fournit des dépendances spécifiques à MainActivity .

4. Dépendances des composants

Les dépendances entre composants permettent à un composant de dépendre d'un autre, ce qui permet la réutilisation des dépendances. Il existe deux principaux types de dépendances entre composants :

  • Sous-composants : Un sous-composant est un enfant d'un autre composant et peut accéder aux dépendances de son parent.
  • Attribut de dépendance : cela permet à un composant de dépendre d'un autre composant sans être un sous-composant.

1. Sous-composants :

Un sous-composant est un enfant d'un autre composant et peut accéder aux dépendances de son parent. Les sous-composants sont définis dans le composant parent et peuvent hériter de sa portée.

Exemple :

 @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }

Dans cet exemple, ActivitySubcomponent est un sous-composant du composant parent et peut accéder à ses dépendances.

2. Attribut de dépendance

Cela permet à un composant de dépendre d'un autre composant sans être un sous-composant. Le composant dépendant peut accéder aux dépendances fournies par le composant parent.

Exemple :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Dans cet exemple, ActivityComponent dépend d' ApplicationComponent et peut accéder à ses dépendances.

5. Liaisons d'exécution

Les liaisons d'exécution dans Dagger 2 font référence à la fourniture de dépendances créées et gérées au moment de l'exécution, en fonction du contexte dans lequel elles sont nécessaires.

  • Contexte d'application : utilisé pour les dépendances qui doivent vivre aussi longtemps que l'application.
  • Contexte d'activité : utilisé pour les dépendances qui doivent vivre aussi longtemps qu'une activité.

1. Contexte d'application

Définition : Le contexte applicatif est un contexte lié au cycle de vie de l'application dans son ensemble. Il est utilisé pour les dépendances qui doivent vivre aussi longtemps que l'application elle-même.

Dépendances partagées dans l'ensemble de l'application et qui n'ont pas besoin d'être recréées pour chaque activité ou fragment. Les exemples incluent les clients réseau, les instances de base de données et les préférences partagées.

Exemple :

 @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }

Dans cet exemple, AppModule fournit le contexte de l'application sous forme de dépendance singleton. La méthode provideApplicationContext garantit que le contexte fourni est lié au cycle de vie de l'application.

2. Contexte de l'activité

Définition : Le contexte d'activité est un contexte lié au cycle de vie d'une activité spécifique. Il est utilisé pour les dépendances qui doivent vivre aussi longtemps que l'activité elle-même.

Dépendances spécifiques à une activité et devant être recréées à chaque fois que l'activité est recréée. Exemples : modèles de vue, présentateurs et dépendances liées à l'interface utilisateur.

Exemple :

 @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }

Dans cet exemple, ActivityModule fournit le contexte d'activité sous forme de dépendance limitée. La méthode provideActivityContext garantit que le contexte fourni est lié au cycle de vie de l'activité.

Utilisation des liaisons d'exécution dans les composants

Pour utiliser ces liaisons d'exécution, vous devez inclure les modules correspondants dans vos composants :

Composant d'application :

 @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }

Composante de l'activité :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }

Injection de contextes

Une fois que vous avez configuré vos composants et modules, vous pouvez injecter les contextes dans vos classes selon vos besoins.

Exemple dans une activité :

 public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }

Dans cet exemple, MainActivity reçoit à la fois le contexte d'activité et le contexte d'application via l'injection de dépendances. Cela permet à l'activité d'utiliser le contexte approprié en fonction des besoins spécifiques des dépendances.

Exemple : Utilisation de Dagger 2 dans une application Android

Configuration de Dagger 2

Pour utiliser Dagger 2 dans votre projet, vous devez ajouter les dépendances suivantes à votre fichier build.gradle :

 dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }

Remplacez 2.x par la dernière version de Dagger 2.

Étape 1 : Définir un module

Créez un module pour fournir des dépendances. Par exemple, un NetworkModule pour fournir une instance Retrofit :

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }

Étape 2 : Définir un composant

Créez un composant pour relier le module et les classes qui ont besoin des dépendances :

 @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Étape 3 : Injecter les dépendances

Utilisez le composant pour injecter des dépendances dans vos classes. Par exemple, dans votre classe Application :

 public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Étape 4 : utiliser les dépendances injectées

Vous pouvez maintenant utiliser les dépendances injectées dans vos classes. Par exemple, dans une Activity :

 public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }

Conclusion

Résumons ce sujet :

  • L’objectif principal de DI est de relâcher le couplage, facilitant ainsi la gestion des dépendances.
  • En utilisant DI, vous pouvez augmenter la flexibilité du code et simplifier le processus de test.
  • DI est un sujet complexe avec différentes implémentations en fonction du scénario.
  • La DI dans différentes langues présente des particularités qui peuvent affecter la façon dont vous travaillez avec elle.
  • Dagger 2 automatise le processus de création et de fourniture de dépendances, réduisant ainsi le code standard et améliorant la maintenabilité.
  • Dagger 2 offre une sécurité au moment de la compilation, garantissant que toutes les dépendances sont satisfaites avant l'exécution de l'application.
  • En générant du code au moment de la compilation, Dagger 2 évite les frais de performances associés aux solutions basées sur la réflexion.