Manuelle Abhängigkeitsinjektionen oder Dienstsuche in einer Android-App können je nach Größe des Projekts problematisch sein. Sie können die Komplexität Ihres Projekts begrenzen, wenn es hochskaliert wird, indem Sie Dagger zur Verwaltung von Abhängigkeiten verwenden.
Dagger generiert automatisch Code, der den Code nachahmt, den Sie sonst von Hand geschrieben hätten. Da der Code bei der Kompilierung generiert wird, ist er nachverfolgbar und leistungsfähiger als andere reflektierende Lösungen wie Guice.
Vorteile von Dagger
Dagger hilft Ihnen, mühsamen und fehleranfälligen Boilerplate-Code zu schreiben:
Generieren des
AppContainer
-Codes (Anwendungsgrafik), den Sie manuell im manuellen DI-Abschnitt implementiert haben.Factorys für die im Anwendungsdiagramm verfügbaren Klassen erstellen So werden Abhängigkeiten intern erfüllt.
Entscheiden, ob eine Abhängigkeit wiederverwendet oder mithilfe von Bereichen eine neue Instanz erstellt werden soll.
Erstellen von Containern für bestimmte Abläufe wie beim Anmeldevorgang im vorherigen Abschnitt mit Dolgger-Unterkomponenten. Dies verbessert die Leistung Ihrer App, da nicht mehr benötigte Objekte im Arbeitsspeicher freigegeben werden.
All dies wird bei der Build-Erstellung automatisch von Dagger ausgeführt, sofern Sie Abhängigkeiten einer Klasse deklarieren und mithilfe von Anmerkungen angeben, wie diese erfüllt werden sollen. Dagger generiert Code ähnlich dem, den Sie manuell geschrieben hätten. Intern erstellt Dagger eine Grafik mit Objekten, auf die er verweisen kann, um eine Instanz einer Klasse bereitzustellen. Für jede Klasse im Diagramm generiert Dagger eine Factory-Typ-Klasse, die intern verwendet wird, um Instanzen dieses Typs abzurufen.
Während der Build-Erstellung geht Dagger Ihren Code durch und:
Erstellt und validiert Abhängigkeitsdiagramme, um sicherzustellen, dass:
- Alle Abhängigkeiten jedes Objekts können erfüllt werden, sodass es keine Laufzeitausnahmen gibt.
- Es gibt keine Abhängigkeitszyklen, sodass es keine Endlosschleifen gibt.
Generiert die Klassen, die zur Laufzeit verwendet werden, um die eigentlichen Objekte und ihre Abhängigkeiten zu erstellen.
Ein einfacher Anwendungsfall in Dagger: Generierung einer Fabrik
Zur Veranschaulichung Ihrer Arbeit mit Dagger erstellen wir eine einfache Factory für die Klasse UserRepository
, wie im folgenden Diagramm dargestellt:
Definieren Sie UserRepository
so:
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; } ... }
Fügen Sie dem Konstruktor UserRepository
eine @Inject
-Annotation hinzu, damit Dagger weiß, wie ein UserRepository
erstellt wird:
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; } }
Im Code-Snippet oben teilen Sie Dagger mit:
Hier erfahren Sie, wie Sie eine
UserRepository
-Instanz mit dem annotierten@Inject
-Konstruktor erstellen.Die Abhängigkeiten sind:
UserLocalDataSource
undUserRemoteDataSource
.
Dagger weiß jetzt, wie eine Instanz von UserRepository
erstellt wird, weiß aber nicht, wie seine Abhängigkeiten erstellt werden. Wenn Sie auch die anderen Klassen annotieren, weiß Dagger, wie sie erstellt werden:
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() { } }
Dolchkomponenten
Dagger kann ein Diagramm der Abhängigkeiten in Ihrem Projekt erstellen, um herauszufinden, wo diese Abhängigkeiten bei Bedarf abgerufen werden sollen.
Dazu müssen Sie eine Schnittstelle erstellen und mit @Component
annotieren. Dagger erstellt einen Container wie bei der manuellen Abhängigkeitsinjektion.
Innerhalb der @Component
-Schnittstelle können Sie Funktionen definieren, die Instanzen der von Ihnen benötigten Klassen zurückgeben (z.B. UserRepository
). @Component
weist Dagger an, einen Container mit allen Abhängigkeiten zu generieren, die zur Erfüllung der verfügbaren Typen erforderlich sind. Dies wird als Dagger-Komponente bezeichnet. Sie enthält eine Grafik, die aus den von Dagger bekannten Objekten und ihren jeweiligen Abhängigkeiten besteht.
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(); }
Wenn Sie das Projekt erstellen, generiert Dagger eine Implementierung der ApplicationGraph
-Schnittstelle für Sie: DaggerApplicationGraph
. Mit dem Annotationsprozessor erstellt Dagger ein Abhängigkeitsdiagramm, das aus den Beziehungen zwischen den drei Klassen (UserRepository
, UserLocalDatasource
und UserRemoteDataSource
) mit nur einem Einstiegspunkt besteht: der Abfrage einer UserRepository
-Instanz. Sie können sie so verwenden:
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 erstellt bei jeder Anfrage eine neue Instanz von 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)
Manchmal benötigen Sie eine eindeutige Instanz einer Abhängigkeit in einem Container. Dies kann aus verschiedenen Gründen erforderlich sein:
Sie möchten, dass andere Typen mit diesem Typ als Abhängigkeit dieselbe Instanz verwenden, z. B. mehrere
ViewModel
-Objekte im Anmeldevorgang mit derselbenLoginUserData
.Die Erstellung eines Objekts ist teuer und Sie möchten nicht jedes Mal eine neue Instanz erstellen, wenn sie als Abhängigkeit deklariert wird (z. B. ein JSON-Parser).
In diesem Beispiel kann es sinnvoll sein, eine eindeutige Instanz von UserRepository
im Diagramm verfügbar zu haben, damit bei jeder Abfrage von UserRepository
immer dieselbe Instanz zurückgegeben wird. Dies ist in Ihrem Beispiel nützlich, da Sie in einer realen Anwendung mit einer komplexeren Anwendungsgrafik möglicherweise mehrere ViewModel
-Objekte haben, die von UserRepository
abhängen, und Sie nicht jedes Mal neue Instanzen von UserLocalDataSource
und UserRemoteDataSource
erstellen möchten, wenn UserRepository
bereitgestellt werden muss.
Bei der manuellen Abhängigkeitsinjektion übergeben Sie dazu dieselbe Instanz von UserRepository
an die Konstruktoren der ViewModel-Klassen. Da Sie diesen Code in Dagger jedoch nicht manuell schreiben, müssen Sie Dagger mitteilen, dass Sie dieselbe Instanz verwenden möchten. Dies ist mit Bereichsanmerkungen möglich.
Umfang mit Dolch festlegen
Sie können Bereichsanmerkungen verwenden, um die Lebensdauer eines Objekts auf die Lebensdauer seiner Komponente zu begrenzen. Das bedeutet, dass jedes Mal, wenn dieser Typ angegeben werden muss, dieselbe Instanz einer Abhängigkeit verwendet wird.
Wenn Sie eine eindeutige Instanz eines UserRepository
haben möchten, wenn Sie das Repository in ApplicationGraph
anfordern, verwenden Sie für die @Component
-Schnittstelle und für UserRepository
dieselbe Bereichsanmerkung. Sie können die Annotation @Singleton
verwenden, die bereits im javax.inject
-Paket von Dagger enthalten ist:
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; } }
Alternativ können Sie eine benutzerdefinierte Bereichsanmerkung erstellen und verwenden. So erstellen Sie eine Bereichsanmerkung:
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
Sie können sie dann wie zuvor verwenden:
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; } }
In beiden Fällen hat das Objekt denselben Bereich, der zum Annotieren der @Component
-Schnittstelle verwendet wird. Daher erhalten Sie bei jedem Aufruf von applicationGraph.repository()
dieselbe Instanz von 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)
Fazit
Es ist wichtig, sich mit den Vorteilen und der Funktionsweise von Dagger vertraut zu machen, bevor Sie die Anwendung in komplizierteren Szenarien einsetzen können.
Auf der nächsten Seite erfahren Sie, wie Sie Dagger einer Android-App hinzufügen.