paint-brush
Abhängigkeitsinjektion mit Dagger 2: Was ist das, Schlüsselkonzepte und mehrvon@dilip2882
Neue Geschichte

Abhängigkeitsinjektion mit Dagger 2: Was ist das, Schlüsselkonzepte und mehr

von Dilip Patel21m2024/08/28
Read on Terminal Reader

Zu lang; Lesen

Dependency Injection (DI) ist ein Entwurfsmuster, das zur Implementierung von Inversion of Control (IoC) verwendet wird. Dabei handelt es sich um eine Technik, bei der die Verantwortung für die Erstellung von Objekten auf andere Teile des Codes übertragen wird. Dies fördert eine lose Kopplung und macht den Code modularer und einfacher zu verwalten.
featured image - Abhängigkeitsinjektion mit Dagger 2: Was ist das, Schlüsselkonzepte und mehr
Dilip Patel HackerNoon profile picture
0-item


Einführung in die Abhängigkeitsinjektion

Dependency Injection (DI) ist ein Entwurfsmuster, das zur Implementierung von Inversion of Control (IoC) verwendet wird, wobei die Kontrolle über die Erstellung und Verwaltung von Abhängigkeiten von der Anwendung auf eine externe Entität übertragen wird. Dies hilft bei der Erstellung modulareren, testbareren und wartbareren Codes. Es handelt sich um eine Technik, bei der die Verantwortung für die Erstellung von Objekten auf andere Teile des Codes übertragen wird. Dies fördert eine lose Kopplung und macht den Code modularer und einfacher zu verwalten.

Klassen benötigen häufig Verweise auf andere Klassen, um richtig zu funktionieren. Betrachten Sie beispielsweise eine Library , die eine Book erfordert. Diese erforderlichen Klassen werden als Abhängigkeiten bezeichnet. Die Library ist für ihre Funktion auf eine Instanz der Book angewiesen.

hyperskill.org

Es gibt drei grundlegende Möglichkeiten für eine Klasse, die benötigten Objekte abzurufen:

  1. Selbstkonstruktion : Die Klasse erstellt und initialisiert ihre eigenen Abhängigkeiten. Beispielsweise würde die Klasse Library ihre eigene Instanz der Klasse Book erstellen und initialisieren.
  2. Externer Abruf : Die Klasse ruft Abhängigkeiten von einer externen Quelle ab. Einige Android-APIs, wie z. B. Context Getter und getSystemService() , funktionieren auf diese Weise.
  3. Abhängigkeitsinjektion : Abhängigkeiten werden der Klasse entweder bei ihrer Erstellung oder durch Methoden, die sie erfordern, bereitgestellt. Beispielsweise würde der Library eine Book als Parameter erhalten.

Die dritte Option ist die Abhängigkeitsinjektion! Mit DI stellen Sie die Abhängigkeiten einer Klasse bereit, anstatt sie von der Klasseninstanz selbst abrufen zu lassen.

Beispiel ohne Abhängigkeitsinjektion

Ohne DI könnte eine Library , die ihre eigene Book erstellt, folgendermaßen aussehen:

 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(); } }

Dies ist kein Beispiel für DI, da die Klasse Library ihr eigenes Book erstellt. Dies kann aus folgenden Gründen problematisch sein:

  • Enge Kopplung : Library und Book sind eng gekoppelt. Eine Instanz von Library verwendet einen Book , was die Verwendung von Unterklassen oder alternativen Implementierungen erschwert.
  • Testschwierigkeiten : Die starke Abhängigkeit von Book macht das Testen anspruchsvoller. Library verwendet eine echte Instanz von Book und verhindert so die Verwendung von Testdoubles, um Book für verschiedene Testfälle zu modifizieren.

Beispiel mit Abhängigkeitsinjektion

Mit DI erstellt nicht jede Instanz von Library ihr eigenes Book Objekt, sondern erhält ein Book Objekt als Parameter in ihrem Konstruktor:

 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(); }

Die Hauptfunktion verwendet Library . Da Library von Book abhängt, erstellt die App eine Instanz von Book und verwendet diese dann, um eine Instanz von Library zu konstruieren. Die Vorteile dieses DI-basierten Ansatzes sind:

  • Wiederverwendbarkeit von Library : Sie können verschiedene Implementierungen von Book an Library übergeben. Sie können beispielsweise eine neue Unterklasse von Book namens EBook definieren, die Library verwenden soll. Mit DI übergeben Sie einfach eine Instanz von EBook an Library und es funktioniert ohne weitere Änderungen.
  • Einfaches Testen der Library : Sie können Test-Doubles übergeben, um verschiedene Szenarien zu testen.

Ein weiteres DI-Beispiel

Stellen Sie sich ein Szenario vor, in dem eine NotificationService Klasse auf einer Notification Klasse basiert. Ohne DI erstellt der NotificationService direkt eine Instanz von Notification , was die Verwendung unterschiedlicher Benachrichtigungstypen oder das Testen des Dienstes mit verschiedenen Benachrichtigungsimplementierungen erschwert.

Um DI zu veranschaulichen, überarbeiten wir dieses Beispiel:

 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(); } }

Jetzt hängt NotificationService von der Notification Schnittstelle ab und nicht von einer bestimmten Klasse. Dadurch können verschiedene Implementierungen von Notification austauschbar verwendet werden. Sie können die Implementierung, die Sie verwenden möchten, über die Methode sendNotification festlegen:

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

Methoden der Abhängigkeitsinjektion in Android

Es gibt drei Haupttypen von DI:

  1. Methodeninjektion (Schnittstelleninjektion) : Abhängigkeiten werden über Methoden übergeben, auf die die Klasse über eine Schnittstelle oder eine andere Klasse zugreifen kann. Das vorherige Beispiel demonstriert die Methodeninjektion.
  2. Konstruktorinjektion : Abhängigkeiten werden der Klasse über ihren Konstruktor übergeben.
 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. Feldinjektion (oder Setter-Injektion) : Bestimmte Android-Framework-Klassen, wie Aktivitäten und Fragmente, werden vom System instanziiert, sodass eine Konstruktorinjektion nicht möglich ist. Bei der Feldinjektion werden Abhängigkeiten nach der Erstellung der Klasse instanziiert.

 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. Methodeninjektion : Abhängigkeiten werden durch Methoden bereitgestellt, häufig unter Verwendung der Annotation @Inject .

Vorteile der Abhängigkeitsinjektion

  • Klassen werden wiederverwendbarer und sind weniger abhängig von bestimmten Implementierungen. Dies ist auf die Umkehrung der Steuerung zurückzuführen, bei der Klassen ihre Abhängigkeiten nicht mehr verwalten, sondern mit jeder bereitgestellten Konfiguration arbeiten.
  • Abhängigkeiten sind Teil der API-Oberfläche und können bei der Objekterstellung oder Kompilierungszeit überprüft werden, was das Refactoring erleichtert.
  • Da eine Klasse ihre Abhängigkeiten nicht verwaltet, können beim Testen unterschiedliche Implementierungen übergeben werden, um verschiedene Szenarien abzudecken.

Automatisierte Abhängigkeitsinjektion

Im vorherigen Beispiel haben Sie die Abhängigkeiten verschiedener Klassen manuell erstellt, bereitgestellt und verwaltet, ohne eine Bibliothek zu verwenden. Dieser Ansatz wird als manuelle Abhängigkeitsinjektion bezeichnet. Während dieser Ansatz in einfachen Fällen funktioniert, wird er umständlich, wenn die Anzahl der Abhängigkeiten und Klassen zunimmt. Die manuelle Abhängigkeitsinjektion hat mehrere Nachteile:

  • Boilerplate-Code : Bei großen Anwendungen kann die Verwaltung aller Abhängigkeiten und deren korrekte Verbindung zu viel sich wiederholendem Code führen. In einer mehrschichtigen Architektur erfordert das Erstellen eines Objekts für eine oberste Schicht die Bereitstellung aller Abhängigkeiten für die darunter liegenden Schichten. Um beispielsweise einen Computer zu bauen, benötigen Sie eine CPU, ein Motherboard, RAM und andere Komponenten; und eine CPU benötigt möglicherweise Transistoren und Kondensatoren.
  • Komplexe Abhängigkeitsverwaltung : Wenn Sie Abhängigkeiten nicht im Voraus erstellen können – etwa bei verzögerten Initialisierungen oder der Beschränkung von Objekten auf bestimmte Flows in Ihrer App – müssen Sie einen benutzerdefinierten Container (oder ein Abhängigkeitsdiagramm) schreiben und verwalten, um die Lebensdauer Ihrer Abhängigkeiten im Speicher zu verwalten.

Bibliotheken können diesen Prozess automatisieren, indem sie Abhängigkeiten für Sie erstellen und bereitstellen. Diese Bibliotheken fallen in zwei Kategorien:

  1. Reflexionsbasierte Lösungen : Diese verbinden Abhängigkeiten zur Laufzeit.
  2. Statische Lösungen : Diese generieren Code, um Abhängigkeiten zur Kompilierungszeit zu verbinden.

Dagger ist eine beliebte, von Google verwaltete Bibliothek zur Abhängigkeitsinjektion für Java, Kotlin und Android. Dagger vereinfacht die Abhängigkeitsinjektion in Ihrer App, indem es den Abhängigkeitsgraphen für Sie erstellt und verwaltet. Es bietet vollständig statische Abhängigkeiten zur Kompilierungszeit und behebt viele der Entwicklungs- und Leistungsprobleme, die mit reflexionsbasierten Lösungen wie Guice verbunden sind.

Reflexionsbasierte Lösungen

Diese Frameworks verbinden Abhängigkeiten zur Laufzeit:

  1. Toothpick : Ein Laufzeit-DI-Framework, das Reflektion zum Verbinden von Abhängigkeiten verwendet. Es ist leicht und schnell konzipiert und daher für Android-Anwendungen geeignet.

Statische Lösungen

Diese Frameworks generieren Code, um Abhängigkeiten zur Kompilierzeit zu verbinden:

  1. Hilt : Hilt basiert auf Dagger und bietet eine Standardmethode, um die Abhängigkeitsinjektion von Dagger in eine Android-Anwendung zu integrieren. Es vereinfacht die Einrichtung und Verwendung von Dagger, indem es vordefinierte Komponenten und Bereiche bereitstellt .
  2. Koin : Ein leichtes und einfaches DI-Framework für Kotlin. Koin verwendet eine DSL zur Definition von Abhängigkeiten und ist einfach einzurichten und zu verwenden.
  3. Kodein : Ein Kotlin-basiertes DI-Framework, das einfach zu verwenden und zu verstehen ist. Es bietet eine einfache und flexible API zur Verwaltung von Abhängigkeiten.

Alternativen zur Abhängigkeitsinjektion

Eine Alternative zur Abhängigkeitsinjektion ist das Service Locator-Muster. Dieses Entwurfsmuster hilft auch dabei, Klassen von ihren konkreten Abhängigkeiten zu entkoppeln. Sie erstellen eine Klasse namens Service Locator, die Abhängigkeiten erstellt und speichert und sie bei Bedarf bereitstellt.

 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() }

Das Service Locator-Muster unterscheidet sich von der Abhängigkeitsinjektion in der Art und Weise, wie Abhängigkeiten genutzt werden. Beim Service Locator-Muster fordern Klassen die Abhängigkeiten an, die sie benötigen. Bei der Abhängigkeitsinjektion stellt die App die erforderlichen Objekte proaktiv bereit.

Was ist Dagger 2?

Dagger 2 ist ein beliebtes DI-Framework für Android. Es verwendet Codegenerierung zur Kompilierungszeit und ist für seine hohe Leistung bekannt. Dagger 2 vereinfacht den Prozess der Abhängigkeitsinjektion, indem es den erforderlichen Code zur Handhabung von Abhängigkeiten generiert, Boilerplate-Code reduziert und die Effizienz verbessert.

Dagger 2 ist eine annotationsbasierte Bibliothek für die Abhängigkeitsinjektion in Android. Hier sind die wichtigsten Annotationen und ihre Zwecke:

  • @Module : Wird verwendet, um Klassen zu definieren, die Abhängigkeiten bereitstellen. Beispielsweise kann ein Modul einen ApiClient für Retrofit bereitstellen.
  • @Provides : Kommentiert Methoden in einem Modul, um anzugeben, wie Abhängigkeiten erstellt und zurückgegeben werden.
  • @Inject : Wird zum Anfordern von Abhängigkeiten verwendet. Kann auf Felder, Konstruktoren und Methoden angewendet werden.
  • @Component : Eine Schnittstelle, die @Module und @Inject verbindet. Sie enthält alle Module und stellt den Builder für die Anwendung bereit.
  • @Singleton : Stellt sicher, dass eine einzelne Instanz einer Abhängigkeit erstellt wird.
  • @Binds : Wird in abstrakten Klassen verwendet, um Abhängigkeiten bereitzustellen, ähnlich wie @Provides , aber prägnanter.

Dolchkomponenten

Dagger kann einen Abhängigkeitsgraphen für Ihr Projekt generieren, sodass es bei Bedarf feststellen kann, wo Abhängigkeiten abgerufen werden können. Um dies zu ermöglichen, müssen Sie eine Schnittstelle erstellen und diese mit @Component kommentieren.

Innerhalb der @Component -Schnittstelle definieren Sie Methoden, die Instanzen der benötigten Klassen zurückgeben (z. B. BookRepository ). Die @Component Annotation weist Dagger an, einen Container mit allen Abhängigkeiten zu generieren, die erforderlich sind, um die von ihm bereitgestellten Typen zu erfüllen. Dieser Container wird als Dagger-Komponente bezeichnet und enthält ein Diagramm von Objekten, die Dagger zusammen mit ihren Abhängigkeiten bereitstellen kann.


Beispiel

Betrachten wir ein Beispiel mit einem LibraryRepository :

  1. Kommentieren Sie den Konstruktor : Fügen Sie dem LibraryRepository Konstruktor eine @Inject -Annotation hinzu, damit Dagger weiß, wie eine Instanz von LibraryRepository erstellt wird.
 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. Abhängigkeiten kommentieren : Kommentieren Sie auf ähnliche Weise die Konstruktoren der Abhängigkeiten ( LocalLibraryDataSource und RemoteLibraryDataSource ), damit Dagger weiß, wie sie erstellt werden.

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

3. Definieren Sie die Komponente : Erstellen Sie eine mit @Component kommentierte Schnittstelle, um den Abhängigkeitsgraphen zu definieren.

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

Wenn Sie das Projekt erstellen, generiert Dagger für Sie eine Implementierung der ApplicationComponent Schnittstelle, die normalerweise DaggerApplicationComponent heißt.

Verwendung

Sie können jetzt die generierte Komponente verwenden, um Instanzen Ihrer Klassen mit automatisch eingefügten Abhängigkeiten abzurufen:

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

In Ihrer Aktivität oder Ihrem Fragment können Sie die LibraryRepository Instanz abrufen:

 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 } }

Schlüsselkonzepte in Dagger 2

1. Module
∘ Schlüsselkonzepte der Module
∘ Module in Komponenten einbinden
2. Geltungsbereich
3. Komponenten
4. Komponentenabhängigkeiten
5. Laufzeitbindungen

1. Module

Module in Dagger 2 sind mit @Module annotierte Klassen, die Abhängigkeiten zu den Komponenten bereitstellen. Sie enthalten mit @Provides oder @Binds annotierte Methoden, um anzugeben, wie Abhängigkeiten erstellt und bereitgestellt werden. Module sind für die Organisation und Verwaltung der Erstellung von Objekten, die Ihre Anwendung benötigt, unerlässlich.

Schlüsselkonzepte der Module

  1. @Module-Annotation: Diese Annotation wird verwendet, um eine Klasse als Dagger-Modul zu definieren. Eine Modulklasse enthält Methoden, die Abhängigkeiten bereitstellen.
  2. @Provides-Annotation: Diese Annotation wird bei Methoden innerhalb eines Moduls verwendet, um anzugeben, dass die Methode eine bestimmte Abhängigkeit bereitstellt. Diese Methoden sind für das Erstellen und Zurückgeben von Instanzen der Abhängigkeiten verantwortlich.
  3. @Binds-Annotation: Diese Annotation wird in abstrakten Klassen verwendet, um eine Implementierung an eine Schnittstelle zu binden. Sie ist prägnanter als @Provides und wird verwendet, wenn das Modul eine abstrakte Klasse ist.

Beispiel eines Moduls

 @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(); } }

In diesem Beispiel ist NetworkModule eine mit @Module annotierte Klasse. Sie enthält zwei mit @Provides annotierte Methoden, die Instanzen von Retrofit und OkHttpClient erstellen und zurückgeben.

Verwenden von @Binds

Wenn Sie eine Schnittstelle und deren Implementierung haben, können Sie @Binds verwenden, um die Implementierung an die Schnittstelle zu binden. Dies ist prägnanter als die Verwendung von @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); }

In diesem Beispiel ist ApiModule eine abstrakte Klasse, die mit @Module annotiert ist. Die Methode bindApiService ist mit @Binds annotiert, um ApiServiceImpl an ApiService zu binden.

Module können basierend auf der von ihnen bereitgestellten Funktionalität organisiert werden. Sie können beispielsweise separate Module für Netzwerkvorgänge, Datenbankvorgänge und UI-bezogene Abhängigkeiten haben.

Beispiel:

  • NetworkModule : Bietet netzwerkbezogene Abhängigkeiten wie Retrofit und OkHttpClient .
  • DatabaseModule : Bietet datenbankbezogene Abhängigkeiten wie RoomDatabase .
  • UIModule : Bietet UI-bezogene Abhängigkeiten wie ViewModel und Presenter .

Einbinden von Modulen in Komponenten

Module werden in Komponenten eingebunden, um Abhängigkeiten für die Klassen bereitzustellen, die sie benötigen. So können Sie dies einrichten:

Anwendungskomponente.java:

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

In diesem Beispiel umfasst ApplicationComponent NetworkModule und DatabaseModule , um Abhängigkeiten für die Anwendung bereitzustellen.

2. Geltungsbereich

Bereiche in Dagger 2 sind Anmerkungen, die den Lebenszyklus von Abhängigkeiten definieren. Sie stellen sicher, dass eine einzelne Instanz einer Abhängigkeit erstellt und innerhalb eines angegebenen Bereichs freigegeben wird. Dies hilft bei der effizienten Verwaltung des Speichers und stellt sicher, dass Abhängigkeiten bei Bedarf wiederverwendet werden.

  • Singleton-Bereich : Stellt sicher, dass während des gesamten Lebenszyklus der Anwendung eine einzelne Instanz einer Abhängigkeit vorhanden ist.
  • Aktivitätsumfang : Stellt eine einzelne Instanz einer Abhängigkeit innerhalb des Lebenszyklus einer Aktivität sicher.
  • Fragmentumfang : Stellt eine einzelne Instanz einer Abhängigkeit innerhalb des Lebenszyklus eines Fragments sicher.

1. Singleton-Bereich

Definition : Der @Singleton -Bereich stellt sicher, dass während des gesamten Lebenszyklus der Anwendung eine einzelne Instanz einer Abhängigkeit erstellt und gemeinsam genutzt wird.

Dieser Bereich wird normalerweise für Abhängigkeiten verwendet, die von der gesamten Anwendung gemeinsam genutzt werden müssen, z. B. Netzwerkclients, Datenbankinstanzen oder gemeinsame Einstellungen.

Beispiel:

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

In diesem Beispiel stellt die Annotation @Singleton sicher, dass die von NetworkModule und DatabaseModule bereitgestellten Retrofit und Database Singletons sind und in der gesamten Anwendung gemeinsam genutzt werden.

2. Tätigkeitsbereich

Definition : @ActivityScope (ein benutzerdefinierter Bereich) stellt sicher, dass innerhalb des Lebenszyklus einer Aktivität eine einzelne Instanz einer Abhängigkeit erstellt und freigegeben wird.

Dieser Bereich ist nützlich für Abhängigkeiten, die spezifisch für eine Aktivität sind und bei jeder Neuerstellung der Aktivität neu erstellt werden sollten, wie z. B. Präsentatoren oder Ansichtsmodelle.

Beispiel :

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

In diesem Beispiel stellt die Annotation @ActivityScope sicher, dass die von ActivityModule bereitgestellten Abhängigkeiten auf den Lebenszyklus der Aktivität beschränkt sind.

3. Fragmentumfang

Definition : @FragmentScope (ein weiterer benutzerdefinierter Bereich) stellt sicher, dass innerhalb des Lebenszyklus eines Fragments eine einzelne Instanz einer Abhängigkeit erstellt und gemeinsam genutzt wird.

Anwendungsfall: Dieser Bereich ist nützlich für Abhängigkeiten, die spezifisch für ein Fragment sind und bei jeder Neuerstellung des Fragments neu erstellt werden sollten, z. B. fragmentspezifische Presenter oder Ansichtsmodelle.

Beispiel :

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

In diesem Beispiel stellt die Annotation @FragmentScope sicher, dass die von FragmentModule bereitgestellten Abhängigkeiten auf den Lebenszyklus des Fragments beschränkt sind.

3. Komponenten

Komponentenabhängigkeiten ermöglichen die Abhängigkeit einer Komponente von einer anderen Komponente und ermöglichen so die Wiederverwendung von Abhängigkeiten. Es gibt zwei Haupttypen von Komponentenabhängigkeiten:

  • Anwendungskomponente : Bietet Abhängigkeiten, die in der gesamten Anwendung benötigt werden.
  • Aktivitätskomponente : Bietet Abhängigkeiten, die innerhalb einer bestimmten Aktivität benötigt werden.

1. Anwendungskomponente

Definition : Die Anwendungskomponente stellt Abhängigkeiten bereit, die in der gesamten Anwendung benötigt werden. Sie wird normalerweise mit @Singleton definiert, um sicherzustellen, dass die Abhängigkeiten in der gesamten Anwendung gemeinsam genutzt werden.

Diese Komponente wird für Abhängigkeiten verwendet, die global verfügbar sein müssen, wie z. B. Netzwerkclients, Datenbankinstanzen oder gemeinsame Einstellungen.

Beispiel :

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

In diesem Beispiel ist die ApplicationComponent für die Bereitstellung Retrofit und Database verantwortlich, die von der gesamten Anwendung gemeinsam genutzt werden.

2. Aktivitätskomponente

Definition : Die Aktivitätskomponente stellt Abhängigkeiten bereit, die innerhalb einer bestimmten Aktivität benötigt werden. Sie wird normalerweise mit einem benutzerdefinierten Bereich wie @ActivityScope versehen, um sicherzustellen, dass die Abhängigkeiten bei jeder Neuerstellung der Aktivität neu erstellt werden.

Diese Komponente wird für Abhängigkeiten verwendet, die spezifisch für eine Aktivität sind, wie etwa Präsentatoren oder Ansichtsmodelle.

Beispiel :

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

In diesem Beispiel ist die ActivityComponent von der ApplicationComponent abhängig und weist spezifische Abhängigkeiten zur MainActivity auf.

4. Komponentenabhängigkeiten

Komponentenabhängigkeiten ermöglichen die Abhängigkeit einer Komponente von einer anderen Komponente und ermöglichen so die Wiederverwendung von Abhängigkeiten. Es gibt zwei Haupttypen von Komponentenabhängigkeiten:

  • Unterkomponenten : Eine Unterkomponente ist das untergeordnete Element einer anderen Komponente und kann auf die Abhängigkeiten ihres übergeordneten Elements zugreifen.
  • Abhängigkeitsattribut : Dadurch kann eine Komponente von einer anderen Komponente abhängig sein, ohne eine Unterkomponente zu sein.

1. Unterkomponenten:

Eine Unterkomponente ist ein untergeordnetes Element einer anderen Komponente und kann auf die Abhängigkeiten ihrer übergeordneten Komponente zugreifen. Unterkomponenten werden innerhalb der übergeordneten Komponente definiert und können deren Umfang erben.

Beispiel :

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

In diesem Beispiel ist ActivitySubcomponent eine Unterkomponente der übergeordneten Komponente und kann auf deren Abhängigkeiten zugreifen.

2. Abhängigkeitsattribut

Dadurch kann eine Komponente von einer anderen Komponente abhängig sein, ohne eine Unterkomponente zu sein. Die abhängige Komponente kann auf die von der übergeordneten Komponente bereitgestellten Abhängigkeiten zugreifen.

Beispiel :

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

In diesem Beispiel ist ActivityComponent von ApplicationComponent abhängig und kann auf deren Abhängigkeiten zugreifen.

5. Laufzeitbindungen

Laufzeitbindungen in Dagger 2 beziehen sich auf die Bereitstellung von Abhängigkeiten, die zur Laufzeit erstellt und verwaltet werden, basierend auf dem Kontext, in dem sie benötigt werden.

  • Anwendungskontext : Wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Anwendung.
  • Aktivitätskontext : Wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie eine Aktivität.

1. Anwendungskontext

Definition : Der Anwendungskontext ist ein Kontext, der an den Lebenszyklus der gesamten Anwendung gebunden ist. Er wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Anwendung selbst.

Abhängigkeiten, die von der gesamten Anwendung gemeinsam genutzt werden und nicht für jede Aktivität oder jedes Fragment neu erstellt werden müssen. Beispiele hierfür sind Netzwerkclients, Datenbankinstanzen und gemeinsame Einstellungen.

Beispiel :

 @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(); } }

In diesem Beispiel stellt AppModule den Anwendungskontext als Singleton-Abhängigkeit bereit. Die Methode provideApplicationContext stellt sicher, dass der bereitgestellte Kontext an den Lebenszyklus der Anwendung gebunden ist.

2. Aktivitätskontext

Definition : Der Aktivitätskontext ist ein Kontext, der an den Lebenszyklus einer bestimmten Aktivität gebunden ist. Er wird für Abhängigkeiten verwendet, die so lange bestehen müssen wie die Aktivität selbst.

Abhängigkeiten, die spezifisch für eine Aktivität sind und bei jeder Neuerstellung der Aktivität neu erstellt werden sollten. Beispiele hierfür sind Ansichtsmodelle, Präsentatoren und UI-bezogene Abhängigkeiten.

Beispiel :

 @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; } }

In diesem Beispiel stellt ActivityModule den Aktivitätskontext als bereichsbezogene Abhängigkeit bereit. Die Methode provideActivityContext stellt sicher, dass der bereitgestellte Kontext an den Lebenszyklus der Aktivität gebunden ist.

Verwenden von Runtimebindungen in Komponenten

Um diese Laufzeitbindungen zu verwenden, müssen Sie die entsprechenden Module in Ihre Komponenten einbinden:

Anwendungskomponente :

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

Aktivitätskomponente :

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

Einfügen von Kontexten

Nachdem Sie Ihre Komponenten und Module eingerichtet haben, können Sie die Kontexte nach Bedarf in Ihre Klassen einfügen.

Beispiel in einer Aktivität :

 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); } }

In diesem Beispiel erhält MainActivity sowohl den Aktivitätskontext als auch den Anwendungskontext durch Abhängigkeitseinfügung. Dadurch kann die Aktivität den geeigneten Kontext basierend auf den spezifischen Anforderungen der Abhängigkeiten verwenden.

Beispiel: Verwenden von Dagger 2 in einer Android-Anwendung

Einrichten von Dagger 2

Um Dagger 2 in Ihrem Projekt zu verwenden, müssen Sie Ihrer Datei build.gradle die folgenden Abhängigkeiten hinzufügen:

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

Ersetzen Sie 2.x durch die neueste Version von Dagger 2.

Schritt 1: Definieren Sie ein Modul

Erstellen Sie ein Modul, um Abhängigkeiten bereitzustellen. Beispielsweise ein NetworkModule , um eine Retrofit Instanz bereitzustellen:

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

Schritt 2: Definieren einer Komponente

Erstellen Sie eine Komponente, um eine Brücke zwischen dem Modul und den Klassen zu schlagen, die die Abhängigkeiten benötigen:

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

Schritt 3: Abhängigkeiten einfügen

Verwenden Sie die Komponente, um Abhängigkeiten in Ihre Klassen einzufügen. Beispielsweise in Ihrer 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; } }

Schritt 4: Eingefügte Abhängigkeiten verwenden

Jetzt können Sie die eingefügten Abhängigkeiten in Ihren Klassen verwenden. Beispielsweise in einer 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 // ... } }

Abschluss

Lassen Sie uns dieses Thema zusammenfassen:

  • Der Hauptzweck von DI besteht darin, die Kopplung zu lockern und so die Verwaltung von Abhängigkeiten zu vereinfachen.
  • Durch die Verwendung von DI können Sie die Codeflexibilität erhöhen und den Testprozess vereinfachen.
  • DI ist ein komplexes Thema mit je nach Szenario unterschiedlichen Implementierungen.
  • DI weist in verschiedenen Sprachen Besonderheiten auf, die sich auf Ihre Arbeit damit auswirken können.
  • Dagger 2 automatisiert den Prozess der Erstellung und Bereitstellung von Abhängigkeiten, reduziert Boilerplate-Code und verbessert die Wartbarkeit.
  • Dagger 2 bietet Sicherheit zur Kompilierungszeit und stellt sicher, dass alle Abhängigkeiten erfüllt sind, bevor die Anwendung ausgeführt wird.
  • Durch die Generierung von Code zur Kompilierzeit vermeidet Dagger 2 den Leistungsmehraufwand, der mit reflexionsbasierten Lösungen verbunden ist.