Dagger in Apps mit mehreren Modulen verwenden

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.

Abbildung 1: Beispiel für ein Dolchdiagramm in einer Projekt mit mehreren Modulen

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.

Abbildung 2: Beispiel für ein Dolchdiagramm in einer mit Funktionsmodulen

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 Modul app 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 Modul app definiert.