Dolche – Grundlagen

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:

  1. Hier erfahren Sie, wie Sie eine UserRepository-Instanz mit dem annotierten @Inject-Konstruktor erstellen.

  2. Die Abhängigkeiten sind: UserLocalDataSource und UserRemoteDataSource.

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:

  1. Sie möchten, dass andere Typen mit diesem Typ als Abhängigkeit dieselbe Instanz verwenden, z. B. mehrere ViewModel-Objekte im Anmeldevorgang mit derselben LoginUserData.

  2. 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.