Ein Projekt mit mehreren Gradle-Modulen wird als Projekt mit mehreren Modulen bezeichnet.
Ein Projekt mit mehreren Modulen, das als einzelnes APK ohne Featuremodule ausgeliefert wird, hat in der Regel ein app
-Modul, das von den meisten Modulen Ihres Projekts abhängen kann, und ein base
- oder core
-Modul, von dem die restlichen Module in der Regel abhängen. Das Modul app
enthält normalerweise Ihre Application
-Klasse, während das Modul base
alle gängigen Klassen enthält, die in allen Modulen Ihres Projekts verwendet werden.
Das Modul app
eignet sich gut, um Ihre Anwendungskomponente (z. B. ApplicationComponent
in der nachfolgenden Abbildung) zu deklarieren. Diese kann Objekte bereitstellen, die andere Komponenten möglicherweise benötigen, sowie die Singletons Ihrer Anwendung. Klassen wie OkHttpClient
, JSON-Parser, Zugriffsfunktionen für Ihre Datenbank oder SharedPreferences
-Objekte, die im core
-Modul definiert werden können, werden beispielsweise über das im app
-Modul definierte ApplicationComponent
-Modul bereitgestellt.
Im app
-Modul kannst du auch andere Komponenten mit kürzerer Lebensdauer verwenden.
Ein Beispiel hierfür ist ein UserComponent
mit nutzerspezifischer Konfiguration (z. B. UserSession
) nach einer Anmeldung.
In den verschiedenen Modulen Ihres Projekts können Sie mindestens eine Unterkomponente mit einer für dieses Modul spezifischen Logik definieren, wie in Abbildung 1 dargestellt.
Sie können beispielsweise in einem login
-Modul einen LoginComponent
-Bereich mit einer benutzerdefinierten @ModuleScope
-Annotation haben, die gemeinsame Objekte für dieses Feature bereitstellen kann, z. B. LoginRepository
. Innerhalb dieses Moduls können Sie auch andere Komponenten haben, die von einer LoginComponent
mit einem anderen benutzerdefinierten Bereich abhängig sind, z. B. @FeatureScope
für einen LoginActivityComponent
oder einen TermsAndConditionsComponent
, wo Sie eine funktionsspezifischere Logik wie ViewModel
-Objekte definieren können.
Für andere Module wie Registration
gehst du ähnlich vor.
Eine allgemeine Regel bei einem Projekt mit mehreren Modulen ist, dass Module derselben Ebene nicht voneinander abhängig sein sollten. Wenn ja, prüfen Sie, ob die gemeinsame Logik (die Abhängigkeiten zwischen ihnen) Teil des übergeordneten Moduls sein sollte. Wenn ja, refaktorieren Sie die Klassen in das übergeordnete Modul. Falls nicht, erstellen Sie ein neues Modul, das das übergeordnete Modul erweitert, und lassen Sie beide ursprünglichen Module des neuen Moduls erweitern.
Als Best Practice empfiehlt es sich, in den folgenden Fällen eine Komponente in einem Modul zu erstellen:
Du musst, wie bei
LoginActivityComponent
, eine Feldeinschleusung durchführen.Sie müssen den Umfang der Objekte festlegen, wie bei
LoginComponent
.
Wenn keine dieser Größen zutrifft und Sie Dagger mitteilen müssen, wie Objekte aus diesem Modul bereitgestellt werden sollen, erstellen Sie ein Dagger-Modul mit den Methoden @Provides
oder @Binds
und stellen Sie es bereit, sofern für diese Klassen keine Injektion der Konstruktion möglich ist.
Implementierung mit Dagger-Unterkomponenten
Auf der Dokumentationsseite Dolgger in Android-Apps verwenden wird beschrieben, wie Unterkomponenten erstellt und verwendet werden. Sie können jedoch nicht denselben Code verwenden, da Featuremodule das Modul app
nicht kennen. Wenn Sie beispielsweise an einen typischen Anmeldevorgang und den Code auf der vorherigen Seite denken, wird keine weitere kompiliert:
Kotlin
class LoginActivity: Activity() { ... 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) ... } }
Java
public class LoginActivity extends Activity { ... @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); ... } }
Der Grund dafür ist, dass das Modul login
weder MyApplication
noch appComponent
kennt. Damit dies funktioniert, müssen Sie im Funktionsmodul eine Schnittstelle definieren, die eine FeatureComponent
enthält, die von MyApplication
implementiert werden muss.
Im folgenden Beispiel kannst du eine LoginComponentProvider
-Schnittstelle definieren, die eine LoginComponent
im login
-Modul für den Anmeldevorgang bereitstellt:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Jetzt verwendet LoginActivity
diese Schnittstelle anstelle des oben definierten Code-Snippets:
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
Jetzt muss MyApplication
diese Schnittstelle und die erforderlichen Methoden implementieren:
Kotlin
class MyApplication: Application(), LoginComponentProvider { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() override fun provideLoginComponent(): LoginComponent { return appComponent.loginComponent().create() } }
Java
public class MyApplication extends Application implements LoginComponentProvider { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); @Override public LoginComponent provideLoginComponent() { return appComponent.loginComponent.create(); } }
So können Sie Dagger-Unterkomponenten in einem Projekt mit mehreren Modulen verwenden. Bei Featuremodulen unterscheidet sich die Lösung durch die Art und Weise, wie die Module voneinander abhängen.
Komponentenabhängigkeiten mit Featuremodulen
Bei Featuremodulen wird die Art und Weise, wie Module normalerweise voneinander abhängig sind, umgekehrt. Anstelle des Moduls app
(einschließlich Featuremodule) hängen die Funktionsmodule vom Modul app
ab. Abbildung 2 zeigt, wie Module strukturiert sind.
In Dagger müssen die Komponenten ihre Unterkomponenten kennen. Diese Informationen sind in einem Dagger-Modul enthalten, das der übergeordneten Komponente hinzugefügt wird (z. B. das Modul SubcomponentsModule
in Dolgger in Android-Apps verwenden).
Aufgrund der umgekehrten Abhängigkeit zwischen der Anwendung und dem Funktionsmodul ist die Unterkomponente leider für das Modul app
nicht sichtbar, da sie sich nicht im Build-Pfad befindet. Beispielsweise kann eine in einem login
-Funktionsmodul definierte LoginComponent
keine Unterkomponente des im app
-Modul definierten ApplicationComponent
sein.
Dagger verfügt über einen Mechanismus namens Komponentenabhängigkeiten, mit dem Sie dieses Problem lösen können. Anstatt dass die untergeordnete Komponente eine Unterkomponente der übergeordneten Komponente ist, hängt die untergeordnete Komponente von der übergeordneten Komponente ab. Es gibt also keine hierarchische Beziehung. Jetzt sind Komponenten von anderen abhängig, um bestimmte Abhängigkeiten zu erhalten. Komponenten müssen Typen aus dem Diagramm verfügbar machen, damit abhängige Komponenten sie verwenden können.
Beispiel: Ein Funktionsmodul namens login
möchte eine LoginComponent
erstellen, die auf dem AppComponent
basiert, der im Gradle-Modul app
verfügbar ist.
Im Folgenden finden Sie Definitionen für die Klassen und die AppComponent
, die Teil des Gradle-Moduls app
sind:
Kotlin
// UserRepository's dependencies class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... } // UserRepository is scoped to AppComponent @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Singleton @Component interface AppComponent { ... }
Java
// UserRepository's dependencies public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } } // UserRepository is scoped to AppComponent @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; } } @Singleton @Component public interface ApplicationComponent { ... }
In Ihrem Gradle-Modul login
, das das Gradle-Modul app
enthält, haben Sie eine LoginActivity
, für die eine LoginViewModel
-Instanz eingeschleust werden muss:
Kotlin
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// LoginViewModel depends on UserRepository that is scoped to AppComponent public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
LoginViewModel
hat eine Abhängigkeit von UserRepository
, die verfügbar und auf AppComponent
beschränkt ist. Erstellen wir eine LoginComponent
, die von AppComponent
abhängig ist, um LoginActivity
einzufügen:
Kotlin
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = [AppComponent::class]) interface LoginComponent { fun inject(activity: LoginActivity) }
Java
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = AppComponent.class) public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
gibt eine Abhängigkeit von AppComponent
an, indem es dem Abhängigkeitenparameter der Komponentenanmerkung hinzugefügt wird. Da LoginActivity
von Dagger eingeschleust wird, fügen Sie der Schnittstelle die Methode inject()
hinzu.
Beim Erstellen eines LoginComponent
muss eine Instanz von AppComponent
übergeben werden. Verwenden Sie dazu die Komponenten-Factory:
Kotlin
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
Java
@Component(dependencies = AppComponent.class) public interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent LoginComponent create(AppComponent appComponent); } void inject(LoginActivity loginActivity); }
Jetzt kann LoginActivity
eine Instanz von LoginComponent
erstellen und die Methode inject()
aufrufen.
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Gets appComponent from MyApplication available in the base Gradle module AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent; // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this); // Now you can access loginViewModel } }
LoginViewModel
hängt von UserRepository
ab. Damit LoginComponent
über AppComponent
darauf zugreifen kann, muss das Laufwerk von AppComponent
auf seiner Schnittstelle verfügbar gemacht werden:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Die Umfangsregeln mit abhängigen Komponenten funktionieren auf die gleiche Weise wie bei Unterkomponenten. Da LoginComponent
eine Instanz von AppComponent
nutzt, können nicht dieselbe Bereichsanmerkung verwendet werden.
Wenn Sie LoginViewModel
auf LoginComponent
beschränken möchten, tun Sie dies wie zuvor mit der benutzerdefinierten Annotation @ActivityScope
.
Kotlin
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
Best Practices
ApplicationComponent
sollte sich immer im Modulapp
befinden.Erstellen Sie Dagger-Komponenten in Modulen, wenn Sie eine Feldeinschleusung in diesem Modul ausführen oder Objekte für einen bestimmten Ablauf Ihrer Anwendung spezifizieren müssen.
Für Gradle-Module, die Dienstprogramme oder Hilfsprogramme sein sollen und kein Diagramm erstellen müssen (aus diesem Grund benötigen Sie eine Dagger-Komponente), sollten Sie öffentliche Dagger-Module mit @Provides- und @Binds-Methoden der Klassen erstellen und freigeben, die keine Konstruktor-Injektion unterstützen.
Wenn Sie Dagger in einer Android-App mit Funktionsmodulen verwenden möchten, können Sie mithilfe von Komponentenabhängigkeiten auf Abhängigkeiten zugreifen, die von der im Modul
app
definiertenApplicationComponent
bereitgestellt werden.