Проект с несколькими модулями Gradle известен как многомодульный проект. В многомодульном проекте, который поставляется как один APK-файл без функциональных модулей, обычно имеется модуль app , который может зависеть от большинства модулей вашего проекта, и base или core модуль, от которого обычно зависят остальные модули. Модуль app обычно содержит класс Application , тогда как base модуль содержит все общие классы, общие для всех модулей вашего проекта.
Модуль app — хорошее место для объявления компонента вашего приложения (например, ApplicationComponent на изображении ниже), который может предоставлять объекты, которые могут понадобиться другим компонентам, а также отдельные элементы вашего приложения. Например, такие классы, как OkHttpClient , анализаторы JSON, средства доступа для вашей базы данных или объекты SharedPreferences , которые могут быть определены в core модуле, будут предоставлены ApplicationComponent , определенным в модуле app .
В модуле app также могут быть другие компоненты с более коротким сроком службы. Примером может быть UserComponent с пользовательской конфигурацией (например, UserSession ) после входа в систему.
В различных модулях вашего проекта вы можете определить хотя бы один подкомпонент с логикой, специфичной для этого модуля, как показано на рисунке 1.

Рисунок 1. Пример графа Dagger в многомодульном проекте
Например, в модуле 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 показано, как структурированы модули.

Рисунок 2. Пример графика Dagger в проекте с функциональными модулями
В 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.