На прошлом уроке мы зафиксировали некоторые недостатки компонента в модуле app и решили для фрагмента создать сабкомпонент в модуле task.

 

 

 

В этот сабкомпонент мы вынесем создание объектов, которые нужны только фрагменту TasksFragment. В нашем случае - это объекты TaskRepository и TaskApi. А вот Database давайте считать общим объектом, который может понадобиться в других модулях. Поэтому его создание мы оставим в AppComponent.

Интерфейс для сабкомпонента у нас уже есть - это TaskComponent. Ранее мы в него оборачивали AppComponent, чтобы TasksFragment мог этот компонент использовать.

Давайте сделаем из него сабкомпонент:

TaskComponent (:task)

@Subcomponent(modules = [NetworkModule::class, TaskModule::class])
interface TaskComponent {
   fun injectTasksFragment(tasksFragment: TasksFragment)
}

Мы добавили ему аннотацию Subcomponent и указали даггер-модули NetworkModule и TaskModule, которые понадобятся для создания объектов TaskRepository и TaskApi.

 

Теперь подправим интерфейс компонента-родителя:

AppComponent (:app)

@Component(modules = [DataModule::class, CoreModule::class])
interface AppComponent {
  
   fun createTaskComponent(): TaskComponent

   @Component.Factory
   interface AppComponentFactory {
       fun create(@BindsInstance context: Context): AppComponent
   }

}

Т.к. создание объектов TaskRepository и TaskApi переехало в сабкомпонент, то из компонента мы можем убрать даггер-модули NetworkModule и TaskModule.

Метод injectTasksFragment в компоненте больше не нужен, т.к. компонент не будет инджектить во фрагмент. Компонент будет создавать сабкомпонент. Поэтому добавляем соответствующий метод createTaskComponent.

 

 

App

Осталось немного доработать класс App. Он продолжит наследовать TaskComponentProvider, чтобы фрагмент из модуля task мог получить TaskComponent. Но если раньше под видом интерфейса TaskComponent мы получали компонент AppComponent, то теперь мы будем получать сабкомпонент TaskComponent:

App (:app)

class App: Application(), TaskComponentProvider {

   lateinit var appComponent: AppComponent

   override fun onCreate() {
       super.onCreate()
       //appComponent = DaggerAppComponent.factory().create(this)
       appComponent = MyAppComponent(this)
   }

   override fun getTaskComponent(): TaskComponent {
       return appComponent.createTaskComponent()
   }
}

В методе getTaskComponent используем компонент AppComponent, чтобы создать и вернуть сабкомпонент TaskComponent.

 

 

Реализация 

Даггер по этим интерфейсам создаст классы компонента и сабкомпонента. И мы сделаем то же самое в нашем компоненте:

MyAppComponent (:app)

class MyAppComponent(private val context: Context) : AppComponent {

   private val dataModule = DataModule()
   private val coreModule = CoreModule()

   fun getDatabase(): Database {
       return dataModule.provideDatabase(
           context,
           coreModule.provideFileManager()
       )
   }

   override fun createTaskComponent(): TaskComponent {
       return MyTaskComponent(this)
   }

   private class MyTaskComponent(
       private val myAppComponent: MyAppComponent,
       private val networkModule: NetworkModule = NetworkModule(),
       private val taskModule: TaskModule = TaskModule()
   ) : TaskComponent {
       override fun injectTasksFragment(tasksFragment: TasksFragment) {
           tasksFragment.taskRepository = taskModule.provideTaskRepository(
               myAppComponent.getDatabase(),
               networkModule.provideTasksApi()
           )
       }
   }

}

В даггере класс сабкомпонента генерируется внутри класса компонента-родителя. Мы делаем также. Внутри класса MyAppComponent создаем класс MyTaskComponent для сабкомпонента. В него переехал код создания репозитория и API.

А компонент теперь создает только Database и сабкомпонент.

 

Также обратите внимание на один важный момент. У сабкомпонента есть доступ к компоненту-родителю. Именно так сабкомпонент получает Database для создания репозитория.

Т.е. класс сабкомпонента TasksComponent должен знать про класс AppComponent, чтобы иметь возможность получать от него объекты. Поэтому даггер создает классы компонента и сабкомпонента настолько рядом друг с другом.

Если бы даггер создал класс сабкомпонента в модуле task, то такой сабкомпонент просто не смог бы использовать свой родительский компонент из модуля app для получения объектов. Потому что из модуля task не виден модуль app.

 

 

Фрагмент 

Код фрагмента не меняется:

TaskFragment (:task)

@Inject
lateinit var taskRepository: TaskRepository

override fun onAttach(context: Context) {
   super.onAttach(context)
   (context.applicationContext as TaskComponentProvider)
       .getTaskComponent()
       .injectTasksFragment(this)
}

Но под капотом теперь происходит немного другое. Application класс все еще является TaskComponentProvider, но возвращает он теперь не компонент AppComponent в TaskComponent обертке, а непосредственно сабкомпонент TaskComponent.

 

 

Результат

Мы поменяли конфигурацию компонентов. Для фрагмента мы создали отдельный сабкомпонент.

Это привело к тому, что компонент AppComponent больше не создает TaskApi, т.к. это делает сабкомпонент. Т.е. нам в модуле app теперь вроде как не надо знать про TaskApi из network. И мы можем убрать из модуля app зависимость от модуля network?

Не можем. Давайте смотреть почему.

Схема

Несмотря на то, что интерфейс сабкомпонента TaskComponent находится в модуле task, его реализация находится в модуле app, внутри класса компонента. А значит, создание всех объектов до сих пор происходит в app модуле.

Т.е. создание сабкомпонента в отдельном модуле (task) не переносит код создания объектов в этот модуль. Объекты сабкомпонента создаются в том же модуле (app), где находится компонент-родитель.

 

Но какие-то плюсы от создания сабкомпонента все равно есть. Давайте смотреть что дает нам эта конфигурация по сравнению с предыдущей (без сабкомпонента).

1) Scope
Сабкомпонент дает нам возможность создавать для него свой scope. Теперь можно привязать время жизни сакомпонента к каком-либо экрану в task и получать синглтоны, время жизни которых ограничено этим экраном, а не временем работы приложения.
Этот пункт явно стал лучше.

2) Зависимости
Модуль app все еще должен знать абсолютно все модули, чтобы создавать объекты. Потому что реализация сабкомпонента находится в том же модуле, где и компонент, т.е. в модуле app.

3) Зона ответственности компонента
Если методы инджекта объектов во фрагменты переезжают из компонента в сабкомпоненты, то компонент перестает быть объектом-монстром, который умеет инджектить во все фрагменты. Ответственность разделилась между ним и сабкомпонентами.
Этот пункт тоже стал лучше.

4) Размер класса компонента
Сгенерированный класс компонента все еще остается огромным., т.к. внутри него находятся все реализации сабкомпонентов.

 

Осталось рассмотреть третий вариант конфигурации компонентов. Когда для модуля task мы создаем не сабкомпонент, а отдельный полноценный компонент.


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal