Dagger in Android-Apps verwenden

Auf der Seite Dagger-Grundlagen wurde erläutert, wie Sie mit Dagger die Abhängigkeit automatisieren können. in Ihre App einfügen. Mit Dagger müssen Sie nicht mühsam und fehleranfälligen Boilerplate-Code.

Zusammenfassung der Best Practices

  • Verwenden Sie die Konstruktor-Einschleusung mit @Inject, um dem Dagger Typen hinzuzufügen wann immer dies möglich ist. Falls nicht: <ph type="x-smartling-placeholder">
      </ph>
    • Mit @Binds teilen Sie Dagger mit, welche Implementierung eine Schnittstelle haben soll.
    • Mithilfe von @Provides teilen Sie Dagger mit, wie Klassen in Ihrem Projekt bereitgestellt werden sollen die Ihnen nicht gehören.
  • Module sollten in einer Komponente nur einmal deklariert werden.
  • Benennen Sie die Bereichsannotationen abhängig von der Lebensdauer, in der die -Anmerkung verwendet. Beispiele hierfür sind @ApplicationScope, @LoggedUserScope, und @ActivityScope.

Abhängigkeiten hinzufügen

Wenn Sie Dagger in Ihrem Projekt verwenden möchten, fügen Sie diese Abhängigkeiten Ihrer Anwendung in Ihre build.gradle-Datei. Hier finden Sie die aktuelle Version von Dagger in diesem GitHub-Projekt.

Kotlin

plugins {
  id 'kotlin-kapt'
}

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

Java

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

Dolch in Android

Betrachten Sie eine Android-Beispiel-App mit dem Abhängigkeitsdiagramm aus Abbildung 1.

LoginActivity hängt von LoginViewModel ab, das vom UserRepository,
  die von UserLocalDataSource und UserRemoteDataSource abhängt, die wiederum
  hängt von Retrofit ab.

Abbildung 1: Abhängigkeitsdiagramm des Beispiels Code

Unter Android erstellen Sie normalerweise ein Dagger-Diagramm, das sich in Ihrer Anwendung befindet. , da sich eine Instanz des Diagramms so lange im Arbeitsspeicher befinden soll, App ausgeführt wird. Auf diese Weise wird das Diagramm an den Anwendungslebenszyklus angehängt. In einigen kann es sinnvoll sein, den Anwendungskontext auch im Diagramm. Dazu muss sich die Grafik auch im Klasse Application. Ein Vorteil davon ist, besteht darin, dass die Grafik für andere Android-Framework-Klassen verfügbar ist. Außerdem vereinfacht es das Testen, da Sie eine benutzerdefinierte Application Klasse in Tests.

Da die Schnittstelle, die die Grafik generiert, mit @Component gekennzeichnet ist, können Sie sie ApplicationComponent oder ApplicationGraph nennen. Normalerweise behalten Sie eine Instanz dieser Komponente in Ihrer benutzerdefinierten Application-Klasse und rufen Sie sie auf jedes Mal, wenn Sie das Anwendungsdiagramm benötigen, wie im folgenden Code snippet:

Kotlin

// Definition of the Application graph
@Component
interface ApplicationComponent { ... }

// appComponent lives in the Application class to share its lifecycle
class MyApplication: Application() {
    // Reference to the application graph that is used across the whole app
    val appComponent = DaggerApplicationComponent.create()
}

Java

// Definition of the Application graph
@Component
public interface ApplicationComponent {
}

// appComponent lives in the Application class to share its lifecycle
public class MyApplication extends Application {

    // Reference to the application graph that is used across the whole app
    ApplicationComponent appComponent = DaggerApplicationComponent.create();
}

Da bestimmte Android-Framework-Klassen wie Aktivitäten und Fragmente vom System instanziiert werden, kann Dagger sie nicht für Sie erstellen. Für Aktivitäten Genauer gesagt muss jeder Initialisierungscode in die Methode onCreate() einfließen. Das bedeutet, dass Sie die Annotation @Inject nicht im Konstruktor der -Klasse (Konstruktor-Injektion) wie in den vorherigen Beispielen. Stattdessen mit Field Injection.

Anstatt die Abhängigkeiten zu erstellen, die eine Aktivität in der onCreate() erfordert möchten, dass Dagger diese Abhängigkeiten für Sie auffüllt. Für Feld anwenden, wenden Sie stattdessen die Anmerkung @Inject auf die Felder an, aus dem Dagger-Diagramm abrufen möchten.

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel
}

Java

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;
}

Der Einfachheit halber ist LoginViewModel keine Android-Architekturkomponenten ViewModel sondern nur eine normale Klasse, die als ViewModel fungiert. Weitere Informationen zum Einschleusen dieser Klassen finden Sie im Code in der offiziellen Android Blueprints Dagger-Implementierung in den Branch dev-dagger.

Bei Dagger ist zu beachten, dass injizierte Felder nicht privat sein dürfen. Sie müssen wie im vorherigen Code mindestens die Sichtbarkeit für das Paket privat haben.

Einschleusende Aktivitäten

Dagger muss wissen, dass LoginActivity auf das Diagramm zugreifen muss, um die erforderliche ViewModel angeben. Auf der Seite Dagger-Grundlagen haben Sie @Component-Schnittstelle, um Objekte aus dem Diagramm abzurufen indem Sie Funktionen mit dem Rückgabetyp bereitstellen, den Sie aus dem Diagramm. In diesem Fall müssen Sie Dagger über ein Objekt informieren (LoginActivity). in dem eine Abhängigkeit eingeschleust werden muss. Dazu stellen Sie den Eine Funktion, die das Objekt, das die Injektion anfordert, als Parameter verwendet.

Kotlin

@Component
interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is requesting.
    fun inject(activity: LoginActivity)
}

Java

@Component
public interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is injecting.
    void inject(LoginActivity loginActivity);
}

Diese Funktion teilt Dagger mit, dass LoginActivity auf das Diagramm zugreifen möchte Injektion von Anfragen. Dagger muss alle Abhängigkeiten erfüllen, LoginActivity benötigt (LoginViewModel mit eigenen Abhängigkeiten). Wenn Sie mehrere Klassen haben, die eine Einschleusung anfordern, müssen Sie und deklarieren Sie sie alle in der Komponente mit ihrem genauen Typ. Wenn Sie beispielsweise LoginActivity und RegistrationActivity, die eine Einschleusung anfordern, haben Sie zwei inject() anstelle einer allgemeinen Methode, die beide Fälle abdeckt. Ein generisches Die Methode inject() teilt Dagger nicht mit, was bereitgestellt werden muss. Die Funktionen kann in der Schnittstelle einen beliebigen Namen haben. Sie rufen sie jedoch inject() auf, Das Empfangen des einzufügenden Objekts als Parameter ist eine Konvention in Dagger.

Um ein Objekt in die Aktivität einzufügen, verwenden Sie das inappComponent Ihre Application-Klasse und rufen Sie die Methode inject() auf. Übergeben Sie dabei eine Instanz der Aktivität, die eine Einschleusung anfordert.

Wenn Sie mit Aktivitäten arbeiten, fügen Sie Dagger onCreate()-Methode der Aktivität vor dem Aufruf von super.onCreate(), um Probleme zu vermeiden mit der Fragmentwiederherstellung. Während der Wiederherstellungsphase in super.onCreate() an eine Aktivität Fragmente angehängt werden, die auf Aktivitätsbindungen zugreifen möchten.

Wenn Sie Fragmente verwenden, fügen Sie Dagger in die Datei onAttach() des Fragments ein. . In diesem Fall kann dies vor oder nach dem Aufruf von super.onAttach() erfolgen.

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        (applicationContext as MyApplication).appComponent.inject(this)
        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

// @Inject tells Dagger how to create instances of LoginViewModel
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Java

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        ((MyApplication) getApplicationContext()).appComponent.inject(this);
        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

public class LoginViewModel {

    private final UserRepository userRepository;

    // @Inject tells Dagger how to create instances of LoginViewModel
    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Lassen Sie uns Dagger sagen, wie die übrigen Abhängigkeiten bereitgestellt werden, des Diagramms:

Kotlin

class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor(
    private val loginService: LoginRetrofitService
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

public class UserLocalDataSource {

    @Inject
    public UserLocalDataSource() {}
}

public class UserRemoteDataSource {

    private final LoginRetrofitService loginRetrofitService;

    @Inject
    public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {
        this.loginRetrofitService = loginRetrofitService;
    }
}

Dolchmodule

In diesem Beispiel verwenden Sie die Networking-Bibliothek von Retrofit. „UserRemoteDataSource“ ist von LoginRetrofitService abhängig. Sie können jedoch die Vorgehensweise zum Erstellen einer Instanz von LoginRetrofitService unterscheidet sich von der die Sie bisher gemacht haben. Es ist keine Klasseninstanziierung. ist das das Ergebnis von Retrofit.Builder() aufrufen und verschiedene Parameter übergeben, um Log-in-Dienst hinzu.

Neben der Anmerkung @Inject gibt es noch eine andere Möglichkeit, der Anwendung stellen Sie eine Instanz einer Klasse bereit: die Informationen in den Dagger-Modulen. Ein Dolch Modul ist eine Klasse, die mit @Module annotiert ist. Dort können Sie mit der Annotation @Provides.

Kotlin

// @Module informs Dagger that this class is a Dagger Module
@Module
class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService::class.java)
    }
}

Java

// @Module informs Dagger that this class is a Dagger Module
@Module
public class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return new Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService.class);
    }
}

Module sind eine Möglichkeit, Informationen zur Bereitstellung Objekte. Wie Sie sehen, haben Sie die Klasse NetworkModule aufgerufen, um die Logik der Bereitstellung von netzwerkbezogenen Objekten. Wenn die App maximiert wird, können Sie Hier können Sie auch angeben, wie ein OkHttpClient bereitgestellt wird Gson oder Moshi konfigurieren.

Die Abhängigkeiten einer @Provides-Methode sind die Parameter dieser Methode. Für Die vorherige Methode, LoginRetrofitService, kann ohne Abhängigkeiten bereitgestellt werden. da die Methode keine Parameter hat. Wenn Sie OkHttpClient als Parameter verwendet, muss Dagger eine OkHttpClient-Instanz aus dem um die Abhängigkeiten von LoginRetrofitService zu erfüllen. Beispiel:

Kotlin

@Module
class NetworkModule {
    // Hypothetical dependency on LoginRetrofitService
    @Provides
    fun provideLoginRetrofitService(
        okHttpClient: OkHttpClient
    ): LoginRetrofitService { ... }
}

Java

@Module
public class NetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) {
        ...
    }
}

Damit die Dagger-Grafik von diesem Modul weiß, müssen Sie es der @Component-Schnittstelle so an:

Kotlin

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    ...
}

Java

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    ...
}

Die empfohlene Methode zum Hinzufügen von Typen zum Dagger-Diagramm ist die Verwendung des Konstruktors. Injection (d.h. mit der Annotation @Inject für den Konstruktor der Klasse). Dies ist manchmal nicht möglich und Sie müssen Dagger-Module verwenden. Ein Beispiel wenn Sie wollen, dass Dagger das Ergebnis einer Berechnung verwendet, um zu bestimmen, wie eine Objektinstanz erstellen. Immer wenn eine Instanz davon angegeben werden muss, Typ verwendet, führt Dagger den Code innerhalb der Methode @Provides aus.

So sieht das Dagger-Diagramm im Beispiel jetzt aus:

Diagramm des Diagramms zur Log-in-Aktivität-Abhängigkeit

Abbildung 2: Darstellung des Diagramms mit LoginActivity wird von Dagger eingeschleust

Der Einstiegspunkt für das Diagramm ist LoginActivity. Weil LoginActivity sich einschleust, LoginViewModel, erstellt Dagger einen Graphen, der weiß, wie eine Instanz bereitgestellt wird. von LoginViewModel und rekursiv seiner Abhängigkeiten. Dagger weiß, wie Dies erreichen Sie aufgrund der Anmerkung @Inject in den Klassen -Konstruktor.

Der von Dagger generierte ApplicationComponent enthält einen Factory-Typ , um Instanzen aller Klassen abzurufen, die sie bereitstellen kann. In dieser Beispiel: Delegiert von Dagger an die NetworkModule, die in ApplicationComponent, um eine Instanz von LoginRetrofitService abzurufen.

Dagger-Bereiche

Bereiche wurden auf der Seite Grundlagen von Dagger erwähnt, um eine eindeutige Instanz eines Typs in einer Komponente. Dies wird mit Typs für den Lebenszyklus der Komponente festlegen.

Weil du UserRepository vielleicht in anderen Funktionen der App verwenden möchtest ein neues Objekt erstellen möchten, können Sie als eindeutige Instanz für die gesamte App. Das Gleiche gilt für LoginRetrofitService: Die Erstellung kann teuer sein und Sie möchten auch eindeutige Instanz dieses Objekts, das wiederverwendet wird. Erstellen einer Instanz von UserRemoteDataSource ist nicht so teuer, daher sollten Sie den Umfang auf die Lebenszyklus der Komponente nicht erforderlich ist.

@Singleton ist die einzige Anmerkung zum Umfang, javax.inject-Paket. Sie können damit ApplicationComponent annotieren. und die Objekte, die Sie in der gesamten Anwendung wiederverwenden möchten.

Kotlin

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Module
class NetworkModule {
    // Way to scope types inside a Dagger Module
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}

Java

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

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

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() { ... }
}

Achten Sie darauf, dass beim Anwenden von Bereichen auf Objekte keine Speicherlecks entstehen. Als Solange sich die Bereichskomponente im Arbeitsspeicher befindet, befindet sich das erstellte Objekt im Arbeitsspeicher . Da ApplicationComponent beim Start der App (im Application), wird er beim Löschen der App ebenfalls gelöscht. Das heißt, die eindeutige Instanz von UserRepository verbleibt immer im Arbeitsspeicher, bis Anwendung gelöscht wird.

Dolch-Unterkomponenten

Besteht der Anmeldevorgang, der von einem einzelnen LoginActivity verwaltet wird, aus mehreren Fragmenten fragmentiert wird, sollten Sie in allen Fällen dieselbe Instanz von LoginViewModel Fragmenten. @Singleton kann LoginViewModel nicht annotieren, um die Instanz wiederzuverwenden aus folgenden Gründen:

  1. Die Instanz von LoginViewModel bleibt im Arbeitsspeicher erhalten, nachdem der Ablauf abgeschlossen.

  2. Sie benötigen für jeden Anmeldevorgang eine andere Instanz von LoginViewModel. Wenn sich der Nutzer beispielsweise abmeldet, möchten Sie eine andere Instanz LoginViewModel statt der Instanz, bei der sich der Nutzer angemeldet hat. beim ersten Mal.

Zum Geltungsbereich von LoginViewModel auf den Lebenszyklus von LoginActivity müssen Sie Folgendes erstellen: eine neue Komponente (eine neue Teilgrafik) für den Anmeldevorgang und einen neuen Bereich.

Lassen Sie uns ein Diagramm erstellen, das für den Anmeldevorgang spezifisch ist.

Kotlin

@Component
interface LoginComponent {}

Java

@Component
public interface LoginComponent {
}

Jetzt sollte LoginActivity Injektionen von LoginComponent erhalten, eine anmeldungsspezifische Konfiguration hat. Dadurch entfällt die Verantwortung, LoginActivity aus der Klasse ApplicationComponent.

Kotlin

@Component
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

Java

@Component
public interface LoginComponent {
    void inject(LoginActivity loginActivity);
}

LoginComponent muss auf die Objekte aus ApplicationComponent zugreifen können da LoginViewModel von UserRepository abhängt. Die Art, Dagger mitzuteilen, Sie möchten, dass eine neue Komponente Teil einer anderen Komponente verwendet, Dagger-Unterkomponenten. Die neue Komponente muss eine Unterkomponente des Komponente mit gemeinsam genutzten Ressourcen.

Unterkomponenten sind Komponenten, die den Objektdiagramm eines übergeordnete Komponente. Daher werden alle in der übergeordneten Komponente bereitgestellten Objekte auch in der Unterkomponente bereitgestellt. Auf diese Weise wird ein Objekt aus einer Unterkomponente kann von einem von der übergeordneten Komponente bereitgestellten Objekt abhängen.

Zum Erstellen von Instanzen von Unterkomponenten benötigen Sie eine Instanz der übergeordneten Komponente. Daher werden die von der übergeordneten Komponente für den der Unterkomponente immer noch der übergeordneten Komponente zugeordnet.

Im Beispiel müssen Sie LoginComponent als Unterkomponente von ApplicationComponent. Vermerken Sie dazu LoginComponent mit @Subcomponent:

Kotlin

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    fun inject(loginActivity: LoginActivity)
}

Java

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
public interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    void inject(LoginActivity loginActivity);
}

Sie müssen auch eine Unterkomponenten-Factory innerhalb von LoginComponent definieren, sodass ApplicationComponent weiß, wie Instanzen von LoginComponent erstellt werden.

Kotlin

@Subcomponent
interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
}

Java

@Subcomponent
public interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    void inject(LoginActivity loginActivity);
}

Um Dagger mitzuteilen, dass LoginComponent eine Unterkomponente von ApplicationComponent haben, müssen Sie dies folgendermaßen angeben:

  1. Erstellen eines neuen Dagger-Moduls (z.B. SubcomponentsModule) mit dem Klasse der Unterkomponente mit dem Attribut subcomponents der Anmerkung.

    Kotlin

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent::class)
    class SubcomponentsModule {}
    

    Java

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent.class)
    public class SubcomponentsModule {
    }
    
  2. So fügen Sie das neue Modul (d.h. SubcomponentsModule) zu ApplicationComponent hinzu:

    Kotlin

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    }
    

    Java

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = {NetworkModule.class, SubcomponentsModule.class})
    public interface ApplicationComponent {
    }
    

    ApplicationComponent muss LoginActivity nicht mehr injizieren. da diese Verantwortung jetzt LoginComponent gehört. Du kannst sie also entfernen Die Methode inject() von ApplicationComponent.

    Nutzer von ApplicationComponent müssen wissen, wie Instanzen von LoginComponent. Die übergeordnete Komponente muss in ihrer Benutzeroberfläche eine Methode hinzufügen, damit Nutzer erstellen Instanzen der Unterkomponente aus einer Instanz des übergeordnete Komponente:

  3. Geben Sie die Factory frei, die Instanzen von LoginComponent im Schnittstelle:

    Kotlin

    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    fun loginComponent(): LoginComponent.Factory
    }
    

    Java

    @Singleton
    @Component(modules = { NetworkModule.class, SubcomponentsModule.class} )
    public interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    LoginComponent.Factory loginComponent();
    }
    

Unterkomponenten Bereiche zuweisen

Wenn Sie das Projekt erstellen, können Sie Instanzen von ApplicationComponent erstellen. und LoginComponent. ApplicationComponent ist an den Lebenszyklus der da Sie dieselbe Instanz des Graphen verwenden möchten, die Anwendung sich im Arbeitsspeicher befindet.

Wie ist der Lebenszyklus von LoginComponent? Einer der Gründe, warum Sie LoginComponent liegt daran, dass Sie dieselbe Instanz des LoginViewModel zwischen Log-in-bezogenen Fragmenten. Sie möchten aber auch andere Instanzen von LoginViewModel, wenn ein neuer Anmeldevorgang erfolgt. LoginActivity ist die richtige Lebensdauer für LoginComponent: Für jede neue Aktivität benötigen Sie eine neue Instanz von LoginComponent und Fragmente, die diese Instanz von verwenden können LoginComponent.

Da LoginComponent an den Lebenszyklus LoginActivity angehängt ist, müssen Sie Behalten Sie einen Verweis auf die Komponente in der Aktivität in derselben Weise bei wie die Verweis auf applicationComponent in der Klasse Application. Auf diese Weise können Fragmente darauf zugreifen.

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent
    ...
}

Java

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    ...
}

Beachten Sie, dass die Variable loginComponent nicht mit @Inject annotiert ist. da Sie nicht erwarten, dass diese Variable von Dagger bereitgestellt wird.

Du kannst das ApplicationComponent verwenden, um einen Verweis auf LoginComponent zu erhalten und fügen Sie dann LoginActivity so ein:

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Creation of the login graph using the application graph
        loginComponent = (applicationContext as MyDaggerApplication)
                            .appComponent.loginComponent().create()

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this)

        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

Java

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Creation of the login graph using the application graph
        loginComponent = ((MyApplication) getApplicationContext())
                                .appComponent.loginComponent().create();

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this);

        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

LoginComponent wird in der onCreate()-Methode der Aktivität erstellt und implizit zerstört werden, wenn die Aktivität vernichtet wird.

LoginComponent muss immer dieselbe Instanz von LoginViewModel bereitstellen. jedes Mal, wenn sie angefordert werden. Dazu erstellen Sie eine benutzerdefinierte Anmerkung und sowohl LoginComponent als auch LoginViewModel damit annotieren. Hinweis Sie können die Anmerkung @Singleton nicht verwenden, da sie bereits verwendet wird von der übergeordneten Komponente und das würde das Objekt zu einem Anwendungs-Singleton machen. (eindeutige Instanz für die gesamte Anwendung). Sie müssen eine andere Anmerkung erstellen Umfang.

In diesem Fall hätten Sie den Umfang @LoginScope nennen können, aber es ist nicht sinnvoll, zu üben. Der Name der Bereichsanmerkung sollte nicht explizit für den Zweck angegeben werden, erfüllt. Stattdessen sollten sie nach ihrer Lebensdauer benannt werden, Anmerkungen können von gleichgeordneten Komponenten wie RegistrationComponent wiederverwendet werden. und SettingsComponent. Deshalb solltest du sie stattdessen @ActivityScope nennen. von @LoginScope.

Kotlin

// Definition of a custom scope called ActivityScope
@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Java

// Definition of a custom scope called ActivityScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
public interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Wenn Sie zwei Fragmente haben, die LoginViewModel benötigen, sind beide mit derselben Instanz bereitgestellt. Wenn Sie zum Beispiel eine LoginUsernameFragment und eine LoginPasswordFragment, die gespritzt werden müssen von LoginComponent:

Kotlin

@ActivityScope
@Subcomponent
interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    fun inject(loginActivity: LoginActivity)
    fun inject(usernameFragment: LoginUsernameFragment)
    fun inject(passwordFragment: LoginPasswordFragment)
}

Java

@ActivityScope
@Subcomponent
public interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    void inject(LoginActivity loginActivity);
    void inject(LoginUsernameFragment loginUsernameFragment);
    void inject(LoginPasswordFragment loginPasswordFragment);
}

Die Komponenten greifen auf die Instanz der Komponente zu, die sich im LoginActivity-Objekt. Der Beispielcode für LoginUserNameFragment erscheint in der folgenden Code-Snippet hinzu:

Kotlin

class LoginUsernameFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Java

public class LoginUsernameFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Dasselbe gilt für LoginPasswordFragment:

Kotlin

class LoginPasswordFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Java

public class LoginPasswordFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Abbildung 3 zeigt, wie das Dagger-Diagramm mit der neuen Unterkomponente aussieht. Die Klassen mit einem weißen Punkt (UserRepository, LoginRetrofitService und LoginViewModel) sind diejenigen, die eine eindeutige Instanz für ihre jeweiligen Komponenten haben.

Anwendungsdiagramm nach Hinzufügen der letzten Unterkomponente

Abbildung 3: Darstellung der von Ihnen erstellten Grafik für das Android-App-Beispiel

Sehen wir uns die einzelnen Teile des Diagramms genauer an:

  1. NetworkModule (und dementsprechend LoginRetrofitService) ist enthalten in ApplicationComponent, weil Sie dies in der Komponente angegeben haben.

  2. UserRepository bleibt in ApplicationComponent, da es auf den Bereich ApplicationComponent. Wenn das Projekt wächst, möchten Sie dieselben -Instanz für verschiedene Funktionen (z.B. Registrierung).

    Da UserRepository Teil von ApplicationComponent ist, sind die zugehörigen Abhängigkeiten (d.h. UserLocalDataSource und UserRemoteDataSource) müssen in dieser Komponente hinzu, um Instanzen von UserRepository bereitstellen zu können.

  3. LoginViewModel ist in LoginComponent enthalten, weil dies nur erforderlich ist von den in LoginComponent eingeschleusten Klassen gibt. LoginViewModel ist nicht enthalten in ApplicationComponent, da keine Abhängigkeit in ApplicationComponent erforderlich ist LoginViewModel

    Wenn Sie UserRepository nicht auf ApplicationComponent festgelegt hätten, Dagger hätte UserRepository und die zugehörigen Abhängigkeiten automatisch eingeschlossen als Teil von LoginComponent, da dies derzeit der einzige Ort ist UserRepository wird verwendet.

Neben der Festlegung von Geltungsbereichen für Objekte einem anderen Lebenszyklus ist das Erstellen von Unterkomponenten eine bewährte Methode, um verschiedene Teile Ihrer Anwendung zu kapseln voneinander unterscheiden.

Anwendung so strukturieren, dass je nach Ablauf verschiedene Dagger-Teildiagramme erstellt werden Ihrer App zu einer leistungsstärkeren und skalierbareren Anwendung in Arbeitsspeicher und Startzeit.

Best Practices für das Erstellen eines Dagger-Diagramms

Gehen Sie beim Erstellen des Dagger-Diagramms für Ihre Anwendung so vor:

  • Beim Erstellen einer Komponente sollten Sie sich überlegen, welches Element für die Lebensdauer der Komponente. In diesem Fall befindet sich die Klasse Application in Belastung über ApplicationComponent und LoginActivity ist für LoginComponent.

  • Verwenden Sie den Gültigkeitsbereich nur, wenn dies sinnvoll ist. Eine übermäßige Verwendung des Gültigkeitsbereichs kann sich negativ Auswirkungen auf die Laufzeitleistung Ihrer App: Das Objekt befindet sich so lange im Arbeitsspeicher, da sich die Komponente im Arbeitsspeicher befindet und das Abrufen eines bereichsbezogenen Objekts teurer ist. Wenn Dagger das Objekt bereitstellt, wird die DoubleCheck-Sperre anstelle eines Factory-Anbieter.

Projekt testen, in dem Dagger verwendet wird

Einer der Vorteile von Abhängigkeitsinjektions-Frameworks wie Dagger ist, wird das Testen Ihres Codes erleichtert.

Einheitentests

Sie müssen Dagger für Einheitentests nicht verwenden. Beim Testen einer Klasse, die Konstruktor einschleusen, müssen Sie Dagger nicht verwenden, um diese Klasse zu instanziieren. Sie können den Konstruktor direkt aufrufen und dabei fiktive oder Simulationsabhängigkeiten übergeben. genau so, wie Sie es ohne Anmerkungen tun würden.

Beim Testen von LoginViewModel beispielsweise:

Kotlin

@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

class LoginViewModelTest {

    @Test
    fun `Happy path`() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        val viewModel = LoginViewModel(fakeUserRepository)
        assertEquals(...)
    }
}

Java

@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

public class LoginViewModelTest {

    @Test
    public void happyPath() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        LoginViewModel viewModel = new LoginViewModel(fakeUserRepository);
        assertEquals(...);
    }
}

End-to-End-Tests

Für Integrationstests empfiehlt es sich, einen TestApplicationComponent ist für Tests gedacht. Für Produktion und Tests wird eine andere Komponentenkonfiguration verwendet.

Dies erfordert eine größere Vorabgestaltung der Module im Ihre Anwendung. Die Testkomponente erweitert die Produktionskomponente und werden verschiedene Module installiert.

Kotlin

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class])
interface TestApplicationComponent : ApplicationComponent {
}

Java

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// Component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}

FakeNetworkModule hat eine Fake-Implementierung der ursprünglichen NetworkModule. Dort können Sie gefälschte Instanzen oder Simulationen bereitstellen, die Sie ersetzen möchten.

Kotlin

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
class FakeNetworkModule {
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        return FakeLoginService()
    }
}

Java

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
public class FakeNetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        return new FakeLoginService();
    }
}

In deinen Integrations- oder End-to-End-Tests würdest du einen TestApplication verwenden, der erstellt TestApplicationComponent anstelle eines ApplicationComponent.

Kotlin

// Your test application needs an instance of the test graph
class MyTestApplication: MyApplication() {
    override val appComponent = DaggerTestApplicationComponent.create()
}

Java

// Your test application needs an instance of the test graph
public class MyTestApplication extends MyApplication {
    ApplicationComponent appComponent = DaggerTestApplicationComponent.create();
}

Dann wird diese Testanwendung in einem benutzerdefinierten TestRunner verwendet, mit dem du Instrumentierungstests durchführen. Weitere Informationen hierzu finden Sie in der Dokumentation Dagger im Codelab Ihrer Android-App

Mit Dagger-Modulen arbeiten

Mit Dolchmodulen wird gezeigt, wie Objekte semantisch bereitgestellt werden. Sie können Module in Komponenten oder auch Module in anderen Modulen. Diese Methode ist leistungsstark, kann aber leicht missbraucht werden.

Nachdem ein Modul einer Komponente oder einem anderen Modul hinzugefügt wurde, wird es bereits im Dagger-Diagramm, Dagger kann diese Objekte in dieser Komponente bereitstellen. Bevor Sie ein Modul hinzufügen, prüfen Sie, ob dieses Modul bereits Teil des Dagger-Diagramms ist indem Sie prüfen, ob sie der Komponente bereits hinzugefügt wurde, oder indem Sie das Projekt kompilieren um zu sehen, ob Dagger die erforderlichen Abhängigkeiten für das Modul ermitteln kann.

Gemäß der bewährten Praxis dürfen Module nur einmal in einer Komponente deklariert werden. (außer für spezielle erweiterte Dagger-Anwendungsfälle).

Angenommen, Sie haben Ihr Diagramm auf diese Weise konfiguriert. ApplicationComponent enthält Module1 und Module2 und Module1 enthält ModuleX.

Kotlin

@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = {ModuleX.class})
public class Module1 { ... }

@Module
public class Module2 { ... }

Wenn jetzt Module2 von den Klassen abhängig ist, die von ModuleX bereitgestellt werden. Schlechte Vorgehensweise enthält ModuleX in Module2, weil ModuleX zweimal enthalten ist in wie im folgenden Code-Snippet dargestellt:

Kotlin

// Bad practice: ModuleX is declared multiple times in this Dagger graph
@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module(includes = [ModuleX::class])
class Module2 { ... }

Java

// Bad practice: ModuleX is declared multiple times in this Dagger graph.
@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = ModuleX.class)
public class Module1 { ... }

@Module(includes = ModuleX.class)
public class Module2 { ... }

Führen Sie stattdessen einen der folgenden Schritte aus:

  1. Refaktorieren Sie die Module und extrahieren Sie das gemeinsame Modul in die Komponente.
  2. Neues Modul mit den von beiden Modulen gemeinsam genutzten und extrahierten Objekten erstellen an die Komponente übergeben.

Wenn Sie auf diese Weise nicht refaktorieren, sind viele Module miteinander verbunden. ohne ein klares Organisationsgefühl zu vermitteln. Das macht es schwieriger, woher die einzelnen Abhängigkeiten stammen.

Bewährte Vorgehensweise (Option 1): ModuleX wird einmal im Dagger-Diagramm deklariert.

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleX::class])
interface ApplicationComponent { ... }

@Module
class Module1 { ... }

@Module
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class, ModuleX.class})
public interface ApplicationComponent { ... }

@Module
public class Module1 { ... }

@Module
public class Module2 { ... }

Bewährte Vorgehensweise (Option 2): Häufige Abhängigkeiten von Module1 und Module2 in ModuleX in ein neues Modul namens ModuleXCommon extrahiert, das die in der Komponente enthalten sind. Dann sind zwei weitere Module namens ModuleXWithModule1Dependencies und ModuleXWithModule2Dependencies sind die mit den für jedes Modul spezifischen Abhängigkeiten erstellt wurden. Alle Module einmal im Dagger-Diagramm deklariert.

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class])
interface ApplicationComponent { ... }

@Module
class ModuleXCommon { ... }

@Module
class ModuleXWithModule1SpecificDependencies { ... }

@Module
class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = [ModuleXWithModule1SpecificDependencies::class])
class Module1 { ... }

@Module(includes = [ModuleXWithModule2SpecificDependencies::class])
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class, ModuleXCommon.class})
public interface ApplicationComponent { ... }

@Module
public class ModuleXCommon { ... }

@Module
public class ModuleXWithModule1SpecificDependencies { ... }

@Module
public class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = ModuleXWithModule1SpecificDependencies.class)
public class Module1 { ... }

@Module(includes = ModuleXWithModule2SpecificDependencies.class)
public class Module2 { ... }

Unterstützte Injektion

Assisted Injection ist ein DI-Muster, das zum Erstellen eines Objekts verwendet wird, Einige Parameter können vom DI-Framework bereitgestellt werden, andere müssen übergeben werden bei der Erstellung durch den Nutzer.

In Android tritt dieses Muster häufig auf Bildschirmen mit Details auf, auf denen die ID des anzuzeigendes Element ist nur während der Laufzeit bekannt, nicht während der Kompilierung, wenn Dagger die DI-Grafik generiert. Um mehr über die unterstützte Injektion mit Dagger zu erfahren, finden Sie in der Dagger-Dokumentation.

Fazit

Lesen Sie den Abschnitt mit Best Practices, falls Sie dies noch nicht getan haben. Bis Wie Sie Dagger in einer Android-App verwenden, erfahren Sie im Artikel zur Verwendung von Dagger in einer Android-App. Codelab erhalten.