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.
- Mit
- 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.
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:
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:
Die Instanz von
LoginViewModel
bleibt im Arbeitsspeicher erhalten, nachdem der Ablauf abgeschlossen.Sie benötigen für jeden Anmeldevorgang eine andere Instanz von
LoginViewModel
. Wenn sich der Nutzer beispielsweise abmeldet, möchten Sie eine andere InstanzLoginViewModel
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:
Erstellen eines neuen Dagger-Moduls (z.B.
SubcomponentsModule
) mit dem Klasse der Unterkomponente mit dem Attributsubcomponents
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 { }
So fügen Sie das neue Modul (d.h.
SubcomponentsModule
) zuApplicationComponent
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
mussLoginActivity
nicht mehr injizieren. da diese Verantwortung jetztLoginComponent
gehört. Du kannst sie also entfernen Die Methodeinject()
vonApplicationComponent
.Nutzer von
ApplicationComponent
müssen wissen, wie Instanzen vonLoginComponent
. Die übergeordnete Komponente muss in ihrer Benutzeroberfläche eine Methode hinzufügen, damit Nutzer erstellen Instanzen der Unterkomponente aus einer Instanz des übergeordnete Komponente: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.
Sehen wir uns die einzelnen Teile des Diagramms genauer an:
NetworkModule
(und dementsprechendLoginRetrofitService
) ist enthalten inApplicationComponent
, weil Sie dies in der Komponente angegeben haben.UserRepository
bleibt inApplicationComponent
, da es auf den BereichApplicationComponent
. Wenn das Projekt wächst, möchten Sie dieselben -Instanz für verschiedene Funktionen (z.B. Registrierung).Da
UserRepository
Teil vonApplicationComponent
ist, sind die zugehörigen Abhängigkeiten (d.h.UserLocalDataSource
undUserRemoteDataSource
) müssen in dieser Komponente hinzu, um Instanzen vonUserRepository
bereitstellen zu können.LoginViewModel
ist inLoginComponent
enthalten, weil dies nur erforderlich ist von den inLoginComponent
eingeschleusten Klassen gibt.LoginViewModel
ist nicht enthalten inApplicationComponent
, da keine Abhängigkeit inApplicationComponent
erforderlich istLoginViewModel
Wenn Sie
UserRepository
nicht aufApplicationComponent
festgelegt hätten, Dagger hätteUserRepository
und die zugehörigen Abhängigkeiten automatisch eingeschlossen als Teil vonLoginComponent
, da dies derzeit der einzige Ort istUserRepository
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 überApplicationComponent
undLoginActivity
ist fürLoginComponent
.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:
- Refaktorieren Sie die Module und extrahieren Sie das gemeinsame Modul in die Komponente.
- 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.