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.
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.
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 moduleapp
.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 moduleapp
.