Projekt z wieloma modułami Gradle jest nazywany projektem wielomodułowym.
W przypadku projektu składającego się z wielu modułów, który jest wysyłany jako pojedynczy plik APK bez modułów funkcji, często istnieje moduł app
, który może zależeć od większości modułów projektu oraz moduł base
lub core
, od których zwykle zależą pozostałe moduły. Moduł app
zazwyczaj zawiera klasę Application
, a moduł base
zawiera wszystkie wspólne klasy udostępnione we wszystkich modułach w projekcie.
Moduł app
to dobre miejsce do zadeklarowania komponentu aplikacji (np. ApplicationComponent
na ilustracji poniżej), który może dostarczać obiekty potrzebne innym komponentom oraz jej single. Na przykład klasy takie jak OkHttpClient
, parsery JSON, akcesory bazy danych lub obiekty SharedPreferences
, które mogą być zdefiniowane w module core
, będą dostarczane przez ApplicationComponent
zdefiniowane w module app
.
W module app
mogą też znajdować się inne komponenty o krótszym czasie eksploatacji.
Przykładem może być tu żądanie UserComponent
z konfiguracją specyficzną dla użytkownika (np. UserSession
) po zalogowaniu.
W różnych modułach projektu możesz zdefiniować co najmniej jeden podkomponent, którego logika jest dla niego specyficzna, tak jak to widać na ilustracji 1.
Na przykład moduł login
może zawierać zakres LoginComponent
z niestandardową adnotacją @ModuleScope
, która udostępnia obiekty wspólne dla danej cechy, takie jak LoginRepository
. W jego module mogą znajdować się też inne komponenty zależne od obiektu LoginComponent
o innym zakresie niestandardowym, np. @FeatureScope
dla elementu LoginActivityComponent
lub TermsAndConditionsComponent
, który umożliwia bardziej precyzyjne określenie zakresu logiki, np. obiekty ViewModel
.
W przypadku innych modułów, takich jak Registration
, konfiguracja jest podobna.
Ogólna zasada w przypadku projektu składającego się z wielu modułów jest taka, że moduły tego samego poziomu nie powinny być od siebie zależne. Jeśli tak, zastanów się, czy ta wspólna logika (zależności między nimi) powinna znaleźć się w module nadrzędnym. Jeśli tak, przeprowadź refaktoryzację, aby przenieść klasy do modułu nadrzędnego. Jeśli tak nie jest, utwórz nowy moduł, który rozszerza moduł nadrzędny i oba moduły oryginalne.
Sprawdzoną metodą jest utworzenie komponentu w module w tych przypadkach:
Musisz wprowadzić wstrzykiwanie pól, tak jak w przypadku
LoginActivityComponent
.Musisz określić zakres obiektów, tak jak w przypadku
LoginComponent
.
Jeśli nie ma zastosowania żadna z tych wielkości i musisz wyjaśnić Daggerowi sposób udostępniania obiektów z tego modułu, utwórz i udostępnij moduł Daggera za pomocą metod @Provides
lub @Binds
, jeśli w przypadku tych klas nie można przeprowadzić wstrzykiwania konstrukcji.
Implementacja z podkomponentami Daggera
Strona dokumentu Używanie Daggera w aplikacjach na Androida zawiera informacje o tworzeniu i używaniu podkomponentów. Nie możesz jednak użyć tego samego kodu, ponieważ moduły funkcji nie wiedzą o module app
. Jeśli np. wyobrazisz sobie typowy proces logowania i kod przedstawiony na poprzedniej stronie, już się nie kompiluje:
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 elementach MyApplication
ani appComponent
. Aby wszystko działało, musisz zdefiniować w module funkcji interfejs udostępniający obiekt FeatureComponent
, który musi zaimplementować MyApplication
.
Poniższy przykład pokazuje, jak zdefiniować interfejs LoginComponentProvider
, który udostępnia LoginComponent
w module login
procesu logowania:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Teraz LoginActivity
będzie używać tego interfejsu zamiast fragmentu kodu zdefiniowanego 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 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 używać podkomponentów Daggera w projekcie wielomodułowym. W przypadku modułów funkcji rozwiązanie różni się ze względu na sposób, w jaki moduły są od siebie uzależnione.
Zależności komponentów z modułami funkcji
W modułach funkcji sposób, w jaki moduły zależą od siebie, jest odwrócony. Zamiast modułu app
zawierającego moduły funkcji, moduły funkcji zależą od modułu app
. Na Rysunku 2 przedstawiamy strukturę modułów.
W Dagger komponenty muszą wiedzieć o swoich podkomponentach. Informacje te znajdują się w module Dagger dodanym do komponentu nadrzędnego (takiego jak moduł SubcomponentsModule
w artykule Używanie Daggera w aplikacjach na Androida).
Ponieważ zależność między aplikacją i modułem funkcji jest odwrotna, podkomponent nie jest widoczny w module app
, ponieważ nie znajduje się w ścieżce kompilacji. Na przykład element LoginComponent
zdefiniowany w module cech login
nie może być podkomponentem elementu ApplicationComponent
zdefiniowanego w module app
.
Dagger ma mechanizm o nazwie zależności komponentów, który umożliwia rozwiązanie tego problemu. Komponent podrzędny nie jest więc podkomponentem komponentu nadrzędnego. Komponent podrzędny jest zależny od komponentu nadrzędnego. Nie ma więc relacji nadrzędny-podrzędny. Teraz komponenty zależą od innych, aby uzyskać określone zależności. Aby komponenty zależne mogły korzystać z nich, komponenty muszą ujawniać typy na wykresie.
Na przykład: moduł funkcji o nazwie login
chce utworzyć LoginComponent
, który zależy od parametru AppComponent
dostępnego w module app
Gradle.
Poniżej znajdziesz definicje klas i elementów AppComponent
, które wchodzą w skład modułu app
Gradle:
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 login
Gradle, który zawiera moduł Gradle app
, znajduje się LoginActivity
, który wymaga wstrzykiwania 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; } }
LoginViewModel
wymaga zależności od UserRepository
, która jest dostępna i ma zakres do AppComponent
. Utwórzmy obiekt LoginComponent
zależny od elementu AppComponent
, który wstrzykuje 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 elementu AppComponent
, dodając ją do parametru zależności w adnotacji komponentu. Ponieważ tag LoginActivity
będzie wstrzykiwany przez Daggera, dodaj do interfejsu metodę inject()
.
Przy tworzeniu obiektu LoginComponent
trzeba przekazać instancję AppComponent
. 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
wymaga UserRepository
. Aby usługa LoginComponent
mogła uzyskać do niej dostęp z poziomu AppComponent
, AppComponent
musi ją udostępnić w swoim interfejsie:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Reguły zakresu z komponentami zależnymi działają tak samo jak w przypadku komponentów podrzędnych. LoginComponent
używa instancji AppComponent
, więc nie może używać tej samej adnotacji zakresu.
Jeśli chcesz ustawić zakres LoginViewModel
na LoginComponent
, zrób to tak jak wcześniej, korzystając z 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 moduleapp
.Utwórz komponenty Dagger w modułach, jeśli musisz wstrzykiwać w nich pola lub chcesz określić zakres obiektów dla określonego przepływu aplikacji.
W przypadku modułów Gradle, które mają być narzędziami lub pomocnikami i nie wymagają tworzenia wykresu (właśnie dlatego potrzebny jest komponent Dagger), utwórz i udostępnij publiczne moduły Daggera z metodami @Provides i @Binds w przypadku klas, które nie obsługują wstrzykiwania konstruktora.
Aby używać Daggera w aplikacji na Androida z modułami funkcji, użyj zależności komponentów, aby uzyskać dostęp do zależności określonych przez
ApplicationComponent
zdefiniowane w moduleapp
.