Używanie Daggera w aplikacjach wielomodułowych

Projekt z wieloma modułami Gradle jest nazywany projektem wielomodułowym. w projekcie z wieloma modułami, który jest wysyłany jako pojedynczy plik APK bez żadnych funkcji; często ma moduł app, który zależy od większości projektu, oraz modułów base lub core, których pozostałe które zależą zwykle od modułów i modułów. Moduł app zwykle zawiera Application, a base zawiera wszystkie popularne zajęcia udostępniane we wszystkich modułach w Twoim projekcie.

Moduł app to dobre miejsce do zadeklarowania komponentu aplikacji (na ApplicationComponent na grafice poniżej), które mogą dostarczać obiekty których mogą potrzebować inne komponenty, a także poszczególne elementy aplikacji. Jako klas takich jak OkHttpClient, parsery JSON, akcesorów do bazy danych lub SharedPreferences obiektów, które mogą być zdefiniowane w module core, zostanie dostarczony przez pole ApplicationComponent zdefiniowane w module app.

W module app możesz też mieć inne komponenty o krótszym okresie ważności. Przykładem może być UserComponent z konfiguracją właściwą dla użytkownika. (np. UserSession) po zalogowaniu.

W różnych modułach projektu możesz zdefiniować przynajmniej ma logikę charakterystyczną dla danego modułu, tak jak to widać na rysunku 1.

Rysunek 1. Przykład grafu Daggera w projekt z wielu modułów

Na przykład w module login możesz mieć LoginComponent są ograniczone do niestandardowej adnotacji @ModuleScope, która może dostarczać obiekty wspólne do tej funkcji, np. LoginRepository. W tym module możesz też zawiera inne komponenty zależne od elementu LoginComponent z innym niestandardowym parametrem zakres, na przykład @FeatureScope dla LoginActivityComponent lub TermsAndConditionsComponent, w którym możesz określić zakres bardziej konkretnych funkcji takich jak ViewModel.

W przypadku innych modułów, takich jak Registration, konfiguracja będzie podobna.

W przypadku projektu z wieloma modułami ogólna zasada jest taka, że moduły o tym samym poziomie nie powinny zależeć od siebie nawzajem. Jeśli tak, zastanów się, czy ta wspólna logika (zależności między nimi) powinna być częścią modułu nadrzędnego. Jeśli tak, refaktoryzacji, aby przenieść klasy do modułu nadrzędnego; W przeciwnym razie utwórz nowy moduł który rozszerza moduł nadrzędny i obejmuje oba moduły pierwotne, nowego modułu.

Ogólnie rzecz biorąc, najlepiej jest utworzyć komponent w następujących przypadkach:

  • Musisz wykonać wstrzykiwanie pól, tak jak w przypadku polecenia LoginActivityComponent.

  • Musisz określić zakres obiektów, tak jak w przypadku LoginComponent.

Jeśli nie ma zastosowania żadna z tych reguł i musisz powiedzieć Daggerowi, jak z tego modułu, utwórz i udostępnij moduł Daggera z użyciem @Provides lub @Binds, jeśli wstrzykiwanie kodu konstrukcyjnego nie jest możliwe w przypadku tych klas.

Implementacja z podkomponentami daggera

W dokumencie Używanie daggera w aplikacjach na Androida dowiesz się, jak tworzyć i używać sztyletu podkomponentów. Nie możesz jednak użyć tego samego kodu, ponieważ moduły funkcji, których nie wiem o module app. Jeśli na przykład uważasz, dotyczący typowego procesu logowania się, a kod na poprzedniej stronie nie powoduje nie kompilować:

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);

        ...
    }
}

Powodem jest to, że moduł login nie wie o MyApplication ani o tym, appComponent Aby funkcja działała, musisz zdefiniować interfejs w funkcji moduł FeatureComponent, którego usługa MyApplication potrzebuje do wdrożenia.

Poniższy przykład pozwala zdefiniować interfejs LoginComponentProvider. który zawiera element LoginComponent w module login na potrzeby procesu logowania:

Kotlin

interface LoginComponentProvider {
    fun provideLoginComponent(): LoginComponent
}

Java

public interface LoginComponentProvider {
   public LoginComponent provideLoginComponent();
}

Teraz LoginActivity użyje tego interfejsu zamiast fragmentu kodu zdefiniowane powyżej:

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);

        ...
    }
}

Teraz MyApplication musi wdrożyć ten interfejs i zaimplementować wymagane metody:

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();
  }
}

W ten sposób możesz wykorzystać podkomponenty Daggera w projekcie wielomodułowym. W przypadku modułów funkcji rozwiązanie jest inne ze względu na sposób, które są od siebie zależne.

Zależności komponentów z modułami funkcji

W przypadku modułów funkcji sposób, w jaki moduły zazwyczaj zależą są odwrócone. Zamiast modułu app zawierającego funkcję moduły funkcji zależą od modułu app. Patrz ilustracja 2. pokazują strukturę modułów.

Rysunek 2. Przykład grafu Daggera w projekt z modułami funkcji

W Daggerze komponenty muszą mieć informacje o swoich podkomponentach. Ta informacja jest częścią modułu Daggera dodanego do komponentu nadrzędnego (takiego jak SubcomponentsModule w artykule Używanie daggera w aplikacjach na Androida).

Niestety w odwrotnej zależności między aplikacją a tagiem modułu funkcji, składnik podrzędny nie jest widoczny z modułu app, ponieważ ale nie ma go na ścieżce kompilacji. Przykład: pole LoginComponent zdefiniowane w Moduł funkcji login nie może być podkomponentem modułu ApplicationComponent zdefiniowane w module app.

Dagger ma mechanizm o nazwie zależności komponentów, którego można używać do: do rozwiązania tego problemu. Zamiast elementu podrzędnego, który jest podkomponentem komponentu komponent nadrzędny jest zależny od komponentu nadrzędnego. Na że nie ma relacji nadrzędny-podrzędny; Teraz komponenty zależą od innych aby uzyskać określone zależności. Komponenty muszą udostępniać typy z grafu aby zależne komponenty mogły z nich korzystać.

Na przykład moduł funkcji o nazwie login chce utworzyć LoginComponent, który zależy od funkcji AppComponent dostępnej w Moduł Gradle app.

Poniżej znajdziesz definicje klas oraz AppComponent, które są częścią moduł Gradle w systemie 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 { ... }

W module Gradle login, który zawiera moduł Gradle app, masz LoginActivity, który wymaga wstrzyknięcia instancji LoginViewModel:

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

Funkcja LoginViewModel korzysta z zależności UserRepository, która jest dostępna i zawężony do AppComponent. Utwórzmy LoginComponent, który zależy od AppComponent, aby wstrzyknąć LoginActivity:

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 określa zależność od AppComponent, dodając ją do funkcji zależności w adnotacji komponentu. Ponieważ LoginActivity ma zostać wstrzykiwana przez Daggera, dodaj do interfejsu metodę inject().

Podczas tworzenia obiektu LoginComponent instancja AppComponent musi być zostały przekazane. Aby to zrobić, użyj fabryki komponentów:

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

Teraz LoginActivity może utworzyć instancję LoginComponent i wywołać metodę inject().

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 zależy od UserRepository; oraz dla LoginComponent na dostęp do niego w domenie AppComponent, AppComponent musi udostępnić go w jego interfejs:

Kotlin

@Singleton
@Component
interface AppComponent {
    fun userRepository(): UserRepository
}

Java

@Singleton
@Component
public interface AppComponent {
    UserRepository userRepository();
}

Reguły zakresu z zależnymi komponentami działają tak samo jak podkomponentów. LoginComponent używa instancji AppComponent, nie mogą używać tej samej adnotacji zakresu.

Jeśli chcesz zawęzić zakres LoginViewModel do LoginComponent, wykonaj te czynności co wcześniej były wyświetlane przy użyciu niestandardowej adnotacji @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;
    }
}

Sprawdzone metody

  • Obiekt ApplicationComponent powinien zawsze znajdować się w module app.

  • Jeśli chcesz wykonać wstrzykiwanie pól, utwórz komponenty sztyletu w modułach w tym module lub musisz określić zakres obiektów dla określonego przepływu Twojej aplikacji.

  • Dotyczy modułów Gradle, które mają pełnić funkcję narzędzi lub pomocników i nie wymagają aby utworzyć wykres (to właśnie dlatego potrzebny jest komponent Sztylet), publicznych modułów Daggera z metodami @Provides i @Binds tych klas, które nie obsługują wstrzykiwania przez konstruktor.

  • Aby używać Daggera w aplikacji na Androida z modułami funkcji, użyj komponentu dostęp do zależności dostarczonych przez ApplicationComponent zdefiniowane w module app.