Ein Projekt mit mehreren Gradle-Modulen wird als Projekt mit mehreren Modulen bezeichnet.
In einem Projekt mit mehreren Modulen, das als einzelnes APK ohne Funktion ausgeliefert wird
Module ist es üblich, ein app
-Modul zu haben, das von den meisten
Module des Projekts und ein base
- oder core
-Modul, das der Rest des
und Module in der Regel davon abhängen. Das Modul app
enthält normalerweise
Application
-Klasse, während die base
-Klasse
Modul enthält alle allgemeinen Klassen, die in allen Modulen Ihres Projekts gemeinsam genutzt werden.
Das Modul app
eignet sich gut, um Ihre Anwendungskomponente zu deklarieren (für
Beispiel, ApplicationComponent
im Bild unten), die Objekte zur Verfügung stellen können,
die andere Komponenten benötigen
sowie die Singleton-Formate Ihrer App. Als
Klassen wie OkHttpClient
, JSON-Parser, Zugriffsfunktionen für Ihre Datenbank,
oder SharedPreferences
-Objekte, die im Modul core
definiert werden können,
wird von der ApplicationComponent
bereitgestellt, die im Modul app
definiert ist.
Im Modul app
sind möglicherweise noch weitere Komponenten mit kürzerer Lebensdauer enthalten.
Ein Beispiel könnte eine UserComponent
mit einer nutzerspezifischen Konfiguration sein
(z. B. UserSession
) nach der Anmeldung.
In den verschiedenen Modulen Ihres Projekts können Sie mindestens ein Unterkomponente mit einer spezifischen Logik für dieses Modul, wie in Abbildung 1 dargestellt.
In einem login
-Modul könnten Sie z. B. ein LoginComponent
haben.
Umfang mit einer benutzerdefinierten @ModuleScope
-Annotation, die gängige Objekte bereitstellen kann
zu dieser Funktion hinzuzufügen, z. B. LoginRepository
. Innerhalb dieses Moduls können Sie
haben andere Komponenten, die von einer LoginComponent
mit einem anderen benutzerdefinierten
Bereich, z. B. @FeatureScope
für LoginActivityComponent
oder einen
TermsAndConditionsComponent
, wo Sie funktionsspezifische Logik einschränken können
wie z. B. ViewModel
-Objekte.
Für andere Module wie Registration
würden Sie eine ähnliche Einrichtung vornehmen.
Bei Projekten mit mehreren Modulen gilt in der Regel, dass Module nicht voneinander abhängig sein sollten. Ist dies der Fall, überlegen Sie, (die Abhängigkeiten zwischen ihnen) sollten Teil des übergeordneten Moduls sein. Wenn ja, refactor, um die Klassen in das übergeordnete Modul zu verschieben; Falls nicht, erstellen Sie ein neues Modul erweitert das übergeordnete Modul, wobei beide ursprünglichen Module das neuen Moduls.
Als Best Practice würden Sie im Allgemeinen eine Komponente in einem in den folgenden Fällen:
Sie müssen wie bei
LoginActivityComponent
Felder einfügen.Sie müssen den Umfang von Objekten wie bei
LoginComponent
festlegen.
Wenn keine dieser beiden Fällen zutrifft und Sie Dagger mitteilen müssen,
aus diesem Modul zu erstellen, erstellen Sie ein Dagger-Modul mit @Provides
oder
@Binds
-Methoden, wenn für diese Klassen keine Konstruktionsinjektion möglich ist.
Implementierung mit Dagger-Unterkomponenten
Auf der Seite Verwenden von Dagger in Android-Apps erfahren Sie, wie Sie
Unterkomponenten. Sie können jedoch nicht denselben Code verwenden,
Funktionsmodule kennen das app
-Modul noch nicht. Wenn Sie beispielsweise
über einen typischen Anmeldevorgang und den Code auf der vorherigen Seite erfährt,
mehr kompilieren:
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 ist, dass das Modul login
weder MyApplication
noch
appComponent
Damit dies funktioniert, müssen Sie eine Schnittstelle in der Funktion definieren
Modul, das ein FeatureComponent
bereitstellt, das MyApplication
benötigt
zu implementieren.
Im folgenden Beispiel können Sie eine LoginComponentProvider
-Schnittstelle definieren
, der im Modul login
für den Anmeldevorgang ein LoginComponent
bereitstellt:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
LoginActivity
verwendet jetzt diese Schnittstelle anstelle des Code-Snippets.
wie oben definiert:
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); ... } }
MyApplication
muss jetzt diese Schnittstelle implementieren und den
erforderliche Methoden:
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 Funktionsmodulen unterscheidet sich die Lösung durch die Art und Weise, Module voneinander abhängig sind.
Komponentenabhängigkeiten mit Funktionsmodulen
Bei Funktionsmodulen ist die Art und Weise, wie Module normalerweise abhängig sind
aufeinander ausgerichtet sind. Anstelle des app
-Moduls mit Feature
sind die Funktionsmodule vom app
-Modul abhängig. Siehe Abbildung 2
für eine Darstellung dessen,
wie Module aufgebaut sind.
In Dagger müssen Komponenten über ihre Unterkomponenten Bescheid wissen. Diese Informationen
ist in einem Dagger-Modul enthalten, das der übergeordneten Komponente (z. B. dem
Modul SubcomponentsModule
in Dagger in Android-Apps verwenden).
Aufgrund der umgekehrten Abhängigkeit zwischen App und
Funktionsmodul verwenden, ist die Unterkomponente im Modul app
nicht sichtbar, weil
nicht im Build-Pfad. Beispiel: Ein LoginComponent
, der in einem
Funktionsmodul von login
darf keine Unterkomponente von
ApplicationComponent
wird im Modul app
definiert.
Dagger verfügt über einen Mechanismus namens Komponentenabhängigkeiten, mit dem Sie um das Problem zu lösen. Anstatt die untergeordnete Komponente als Unterkomponente des übergeordnete Komponente hängt die untergeordnete Komponente von der übergeordneten Komponente ab. Mit dass es keine Über-/Untergeordnet-Beziehung gibt. Komponenten hängen jetzt von anderen ab. um bestimmte Abhängigkeiten zu erhalten. Komponenten müssen Typen aus dem Diagramm sichtbar machen für abhängige Komponenten.
Beispiel: Ein Funktionsmodul namens login
möchte ein
LoginComponent
, die von den AppComponent
abhängig ist, die in der
app
Gradle-Modul.
Im Folgenden finden Sie Definitionen für die Klassen und die AppComponent
, die Teil von sind.
das Gradle-Modul app
:
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 login
-Gradle-Modul, das das app
-Gradle-Modul enthält, haben Sie
LoginActivity
, für das 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 ist und
für AppComponent
. Lassen Sie uns eine LoginComponent
erstellen, die
AppComponent
, 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 zur
Abhängigkeiten der Komponentenanmerkung fest. Weil LoginActivity
von Dagger eingeschleust werden soll, fügen Sie der Schnittstelle die Methode inject()
hinzu.
Beim Erstellen einer LoginComponent
muss eine Instanz von AppComponent
übergeben wurde. Verwenden Sie dazu die Component 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
inject()
-Methode.
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. und für LoginComponent
auf AppComponent
zugreifen können, muss AppComponent
es in
dessen Schnittstelle:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Die Bereichsregeln mit abhängigen Komponenten
Unterkomponenten. Da LoginComponent
eine Instanz von AppComponent
verwendet,
nicht dieselbe Bereichsanmerkung verwenden.
Wenn Sie LoginViewModel
auf LoginComponent
beschränken möchten, gehen Sie so vor:
die Sie zuvor mit der benutzerdefinierten Anmerkung @ActivityScope
gemacht haben.
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.Dagger-Komponenten in Modulen erstellen, wenn Sie Feldeinschleusungen ausführen müssen oder Sie müssen Objekte für einen bestimmten Ablauf Ihre Anwendung.
Für Gradle-Module, die als Dienstprogramme oder Hilfsprogramme gedacht sind und keine ein Diagramm erstellen (daher benötigen Sie eine Dolch-Komponente), öffentliche Dagger-Module mit den Methoden @Provides und @Binds der Klassen, unterstützen keine Konstruktor-Einschleusung.
Wenn Sie Dagger in einer Android-App mit Funktionsmodulen verwenden möchten, verwenden Sie die Komponente Zugriff auf die vom System bereitgestellten
ApplicationComponent
wird im Modulapp
definiert.