Проект с несколькими модулями Gradle известен как многомодульный проект. В многомодульном проекте, который поставляется как один APK-файл без функциональных модулей, обычно имеется модуль app
, который может зависеть от большинства модулей вашего проекта, и base
или core
модуль, от которого обычно зависят остальные модули. Модуль app
обычно содержит класс Application
, тогда как base
модуль содержит все общие классы, общие для всех модулей вашего проекта.
Модуль app
— хорошее место для объявления компонента вашего приложения (например, ApplicationComponent
на изображении ниже), который может предоставлять объекты, которые могут понадобиться другим компонентам, а также отдельные элементы вашего приложения. Например, такие классы, как OkHttpClient
, анализаторы JSON, средства доступа для вашей базы данных или объекты SharedPreferences
, которые могут быть определены в core
модуле, будут предоставлены ApplicationComponent
, определенным в модуле app
.
В модуле app
также могут быть другие компоненты с более коротким сроком службы. Примером может быть UserComponent
с пользовательской конфигурацией (например, UserSession
) после входа в систему.
В различных модулях вашего проекта вы можете определить хотя бы один подкомпонент с логикой, специфичной для этого модуля, как показано на рисунке 1.
Например, в модуле login
вы можете иметь область LoginComponent
с пользовательской аннотацией @ModuleScope
, которая может предоставлять объекты, общие для этой функции, такие как LoginRepository
. Внутри этого модуля вы также можете иметь другие компоненты, которые зависят от LoginComponent
с другой настраиваемой областью действия, например @FeatureScope
для LoginActivityComponent
TermsAndConditionsComponent
, где вы можете использовать более специфичную для функций логику, например объекты ViewModel
.
Для других модулей, таких как Registration
, у вас будет аналогичная настройка.
Общее правило многомодульного проекта заключается в том, что модули одного уровня не должны зависеть друг от друга. Если да, подумайте, должна ли эта общая логика (зависимости между ними) быть частью родительского модуля. Если да, выполните рефакторинг, чтобы переместить классы в родительский модуль; если нет, создайте новый модуль, расширяющий родительский модуль, и оба исходных модуля расширят новый модуль.
Рекомендуется создавать компонент в модуле в следующих случаях:
Вам необходимо выполнить внедрение полей, как и в случае с
LoginActivityComponent
.Вам необходимо определить область действия объектов, как и в случае с
LoginComponent
.
Если ни один из этих случаев не подходит и вам нужно указать Dagger, как предоставлять объекты из этого модуля, создайте и откройте модуль Dagger с методами @Provides
или @Binds
, если внедрение конструкции для этих классов невозможно.
Реализация с помощью подкомпонентов Dagger
На странице документации «Использование Dagger в приложениях Android» рассказывается, как создавать и использовать подкомпоненты. Однако вы не можете использовать один и тот же код, поскольку функциональные модули не знают о модуле app
. Например, если вы думаете о типичном процессе входа в систему и коде, который есть на предыдущей странице, он больше не компилируется:
Котлин
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) ... } }
Ява
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); ... } }
Причина в том, что модуль login
не знает ни о MyApplication
, ни appComponent
. Чтобы это работало, вам необходимо определить интерфейс в функциональном модуле, который предоставляет FeatureComponent
, который MyApplication
должен реализовать.
В следующем примере вы можете определить интерфейс LoginComponentProvider
, который предоставляет LoginComponent
в модуле login
для потока входа в систему:
Котлин
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Ява
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Теперь LoginActivity
будет использовать этот интерфейс вместо фрагмента кода, определенного выше:
Котлин
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Ява
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
Теперь MyApplication
необходимо реализовать этот интерфейс и необходимые методы:
Котлин
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() } }
Ява
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(); } }
Вот как вы можете использовать подкомпоненты Dagger в многомодульном проекте. С функциональными модулями решение отличается из-за того, что модули зависят друг от друга.
Зависимости компонентов с функциональными модулями
С функциональными модулями способ, которым модули обычно зависят друг от друга, меняется на противоположный. Вместо модуля app
, включающего функциональные модули, функциональные модули зависят от модуля app
. На рисунке 2 показано, как структурированы модули.
В Dagger компоненты должны знать о своих подкомпонентах. Эта информация включена в модуль Dagger, добавленный к родительскому компоненту (например, модуль SubcomponentsModule
в разделе «Использование Dagger в приложениях Android »).
К сожалению, из-за обратной зависимости между приложением и функциональным модулем подкомпонент не виден из модуля app
, поскольку его нет в пути сборки. Например, LoginComponent
, определенный в модуле функции login
не может быть подкомпонентом ApplicationComponent
, определенного в модуле app
.
В Dagger есть механизм, называемый зависимостями компонентов , который вы можете использовать для решения этой проблемы. Вместо того, чтобы дочерний компонент был подкомпонентом родительского компонента, дочерний компонент зависит от родительского компонента. При этом не существует отношений между родителями и детьми; теперь компоненты зависят от других, чтобы получить определенные зависимости . Компонентам необходимо предоставлять типы из графа, чтобы зависимые компоненты могли их использовать.
Например: функциональный модуль под названием login
хочет создать LoginComponent
, который зависит от AppComponent
доступного в модуле Gradle app
.
Ниже приведены определения классов и AppComponent
, которые являются частью модуля Gradle app
:
Котлин
// 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 { ... }
Ява
// 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 { ... }
В вашем модуле градиента login
, который включает модуль градиента app
, у вас есть LoginActivity
, для которого требуется внедрение экземпляра LoginViewModel
:
Котлин
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Ява
// 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
имеет зависимость от UserRepository
, которая доступна и ограничена AppComponent
. Давайте создадим LoginComponent
, который зависит от AppComponent
для внедрения LoginActivity
:
Котлин
// 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) }
Ява
// 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
указывает зависимость от AppComponent
, добавляя ее в параметр зависимостей аннотации компонента. Поскольку LoginActivity
будет внедрен Dagger, добавьте в интерфейс метод inject()
.
При создании LoginComponent
необходимо передать экземпляр AppComponent
. Для этого используйте фабрику компонентов:
Котлин
@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) }
Ява
@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); }
Теперь LoginActivity
может создать экземпляр LoginComponent
и вызвать метод inject()
.
Котлин
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 } }
Ява
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
зависит от UserRepository
; и чтобы LoginComponent
мог получить к нему доступ из AppComponent
, AppComponent
должен предоставить его в своем интерфейсе:
Котлин
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Ява
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Правила области действия с зависимыми компонентами работают так же, как и с подкомпонентами. Поскольку LoginComponent
использует экземпляр AppComponent
, они не могут использовать одну и ту же аннотацию области.
Если вы хотите ограничить LoginViewModel
до LoginComponent
, вы должны сделать это, как и раньше, используя пользовательскую аннотацию @ActivityScope
.
Котлин
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Ява
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
Лучшие практики
ApplicationComponent
всегда должен находиться в модулеapp
.Создавайте компоненты Dagger в модулях, если вам нужно выполнить внедрение полей в этот модуль или вам нужно определить область действия объектов для определенного потока вашего приложения.
Для модулей Gradle, которые предназначены для использования в качестве утилит или помощников и не требуют построения графа (вот почему вам понадобится компонент Dagger), создавайте и предоставляйте общедоступные модули Dagger с помощью методов @Provides и @Binds тех классов, которые не используют Не поддерживаю внедрение конструктора.
Чтобы использовать Dagger в приложении Android с функциональными модулями, используйте зависимости компонентов, чтобы иметь возможность доступа к зависимостям, предоставляемым
ApplicationComponent
, определенным в модулеapp
.