Проект с несколькими модулями 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. Например, если вы посмотрите на типичный процесс входа и код, который мы видели на предыдущей странице, то поймете, что он больше не компилируется.
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); ... } }
Причина в том, что модуль login не знает о MyApplication и appComponent. Чтобы это работало, вам нужно определить интерфейс в модуле функции, который предоставляет FeatureComponent, который должен реализовать MyApplication.
В следующем примере вы можете определить интерфейс LoginComponentProvider, который предоставляет LoginComponent в модуле login для процесса входа:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Теперь LoginActivity будет использовать этот интерфейс вместо фрагмента кода, определенного выше:
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); ... } }
Теперь MyApplication должен реализовать этот интерфейс и необходимые методы:
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(); } }
Вот как можно использовать субкомпоненты 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.
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 { ... }
В модуле gradle login, который включает модуль gradle app, есть класс LoginActivity, в который нужно внедрить экземпляр 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 зависит от UserRepository, который доступен и относится к AppComponent. Давайте создадим LoginComponent, который зависит от AppComponent, чтобы внедрить 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 указывает зависимость от AppComponent, добавляя ее в параметр dependencies аннотации компонента. Поскольку LoginActivity будет внедрен Dagger, добавьте метод inject() в интерфейс.
При создании LoginComponent необходимо передать экземпляр AppComponent. Для этого используйте фабрику компонентов:
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); }
Теперь LoginActivity может создать экземпляр LoginComponent и вызвать метод 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 зависит от UserRepository. Чтобы LoginComponent мог получить доступ к нему из AppComponent, AppComponent должен предоставить его в своем интерфейсе:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Правила области действия с зависимыми компонентами работают так же, как и с субкомпонентами. Поскольку LoginComponent использует экземпляр AppComponent, они не могут использовать одну и ту же аннотацию области видимости.
Если вы хотите ограничить область действия LoginViewModel до LoginComponent, вы можете сделать это, как и раньше, с помощью специальной аннотации @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; } }
Рекомендации
ApplicationComponentвсегда должен быть в модулеapp.Создавайте компоненты Dagger в модулях, если вам нужно выполнить внедрение через поле в этом модуле или ограничить область действия объектов для определенного потока вашего приложения.
Для модулей Gradle, которые предназначены для утилит или помощников и не требуют построения графа (для чего вам понадобится компонент Dagger), создайте и предоставьте общедоступные модули Dagger с методами @Provides и @Binds тех классов, которые не поддерживают внедрение через конструктор.
Чтобы использовать Dagger в приложении Android с модулями функций, используйте зависимости компонентов, чтобы получить доступ к зависимостям, предоставляемым
ApplicationComponent, определенным в модулеapp.