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

 

 

 

В чем разница между сабкомпонентом и компонентом? Сабкомпонент знает про свой родительский компонент и может брать от него нужные ему объекты. Обычный компонент не имеет никакого родительского компонента. Чтобы он мог взять объект из другого компонента, нам надо нам надо будет явно прописывать ему dependencies.

В нашем примере сабкомпонент TaskComponent мог брать объект Database у компонента-родителя AppComponent. Но если мы сделаем TaskComponent обычным компонентом (не саб), то он потеряет связь с AppComponent и просто так получить от него Database уже не сможет.

Чтобы это исправить, нам надо будет самим явно указать в dependencies, что компонент TaskComponent хотел бы иметь доступ к объектам AppComponent. И при создании компонента TaskComponent от нас потребуется предоставить ему экземпляр компонента AppComponent.

 

Попробуем это реализовать.

Делаем сабкомпонент обычным компонентом. Для этого в TaskComponent меняем аннотацию Subcomponent на Component, и добавляем AppComponent как dependencies

TaskComponent (:task)

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

Теперь TaskComponent - это компонент, который рассчитывает, что сможет получить от AppComponent объекты, которые сам создать не может. Т.е. объект Database.

Но тут мы снова упираемся в то, что модуль task не знает про модуль app. А значит TaskComponent не знает про AppComponent. Для решения этой проблемы мы снова используем интерфейсы, как мы делали в предыдущих уроках. Один интерфейс нам будет нужен для компонента AppComponent, второй - для App класса.

 

 

Интерфейсы

Создаем первый интерфейс. В нем мы указываем, что хотим получать объект Database

TaskComponentDependencies (:task)

interface TaskComponentDependencies {
   fun getDatabase(): Database
}

Обратите внимание на название. Оно отражает, что в этом интерфейсе указаны объекты, которые нужны компоненту TaskComponent.

Этот интерфейс мы вешаем на компонент AppComponent.

AppComponent (:app)

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

   override fun getDatabase(): Database

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

}

Этот компонент умеет создавать Database, поэтому он справится с задачами TaskComponentDependencies.

 

Прописываем интерфейс TaskComponentDependencies вместо AppComponent как dependencies в TaskComponent

TaskComponent (:task)

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

Теперь компонент TaskComponent знает, что ему будет предоставлена реализация интерфейса TaskComponentDependencies, из которой он сможет получить Database.

 

Угадайте, кто должен предоставить ему эту реализацию? Правильно - мы. Когда мы будем создавать компонент TaskComponent, нам надо будет откуда-то взять готовую реализацию TaskComponentDependencies.

Для этого нам надо от App класса получить AppComponent в виде TaskComponentDependencies. И т.к. нам это сделать в модуле task (который не знает про App класс), то мы создаем интерфейс для App:

TaskComponentDependenciesProvider (:task)

interface TaskComponentDependenciesProvider {
   fun getTaskComponentDependencies(): TaskComponentDependencies
}

От него требуется предоставить нам TaskComponentDependencies.

 

Вешаем этот интерфейс на класс App.

App (:app)

class App: Application(), TaskComponentDependenciesProvider {

   lateinit var appComponent: AppComponent

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

   override fun getTaskComponentDependencies(): TaskComponentDependencies {
       return appComponent
   }

}

Он будет возвращать нам AppComponent как TaskComponentDependencies. А мы используем его при создании компонента TaskComponent.

 

В итоге схема с двумя интерфейсами выглядит так:

Интерфейсы - это обертки для классов модуля app, чтобы модуль task мог работать с этими объектами.

 

Фрагмент через context получит объект TaskComponentDependenciesProvider, который предоставит объект TaskComponentDependencies. А под капотом просто App предоставит AppComponent.

 

 

Реализация

Давайте посмотрим как все это работает под капотом на примере наших реализаций компонентов.

MyAppComponent (:app)

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

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

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

}

Из App компонента уходит внутренний класс сабкомпонента и метод, в котором он создавался и предоставлялся нам. Остается только создание Database

 

Для компонента TaskComponent в модуле task тоже создадим свою реализацию:

MyTaskComponent (:task)

class MyTaskComponent(
   private val taskComponentDependencies: TaskComponentDependencies
): TaskComponent {

   private val networkModule: NetworkModule = NetworkModule()
   private val taskModule: TaskModule = TaskModule()

   override fun injectTasksFragment(tasksFragment: TasksFragment) {
       tasksFragment.taskRepository = taskModule.provideTaskRepository(
           taskComponentDependencies.getDatabase(),
           networkModule.provideTasksApi()
       )
   }
}

На вход он требует TaskComponentDependencies из которого берет Database и использует при создании репозитория. Все остальные объекты он создает сам с помощью даггер-модулей.

 

 

Фрагмент

Собираем все это во фрагменте.

TasksFragment (:app)

   override fun onAttach(context: Context) {
       super.onAttach(context)
       val taskComponentDependencies = (context.applicationContext as TaskComponentDependenciesProvider)
           .getTaskComponentDependencies()
//        val taskComponent = DaggerTaskComponent.builder()
//            .taskComponentDependencies(taskComponentDependencies)
//            .build()
       val taskComponent = MyTaskComponent(taskComponentDependencies)
       taskComponent.injectTasksFragment(this)
   }

Из context получаем объект App в виде TaskComponentDependenciesProvider. От него получаем AppComponent в виде TaskComponentDependencies, и передаем его в создаваемый здесь TaskComponent, который затем инджектит объекты во фрагмент.

 

В итоге получается такая схема:

 

Выделим основные моменты:

Фрагмент использует репозиторий. А репозиторий использует TaskApi и Database. Модулю task необходимо знать про модули network и data.

 

App компонент создает Database. Для этого модулю app нужны модули data и core. А вот TaskApi тут больше не создается и никак не используется. Эта логика переехала в новый компонент в модуль task. Поэтому модулю app больше не надо знать про модуль network.

 

Task компонент использует App компонент, чтобы получить Database, сам создает TaskApi и в итоге создает репозиторий.

 

Фрагмент использует TaskComponent, чтобы получить созданный репозиторий.

 

 

Результат

Мы превратили сабкомпонент в обычный компонент. Это привело к тому, что он потерял прямой доступ к компоненту AppComponent. Чтобы это исправить мы применили уже знакомую нам схему с двумя интерфейсами, после чего TaskComponent смог использовать AppComponent для получения объекта Database.


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

1) Scope
Этот пункт остается без изменений. Как и сабкомпонент, отдельный компонент дает нам возможность создавать для него свой scope.

2) Зависимости
Этот пункт стал лучше. Модуль app теперь должен знать меньше модулей, потому что сборка объектов частично переехала в модуль task в класс нового компонента.
Модуль network теперь не используется в app.

 

3) Зона ответственности компонента
Тут также без изменений. Ответственность за инджект объектов в фрагменты поделена между компонентами.

4) Размер класса компонента
Тут у нас улучшение. Класс App компонента похудел, т.к. часть его кода (создание репозитория) ушла в класс Task компонента. И это действительно отдельный класс в отличие от класса сабкомпонента.

 

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

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

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

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

- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме 




Комментарии   

# DifficultSwenyly 29.08.2022 10:16
Quite difficult but I find it interesting and like fleeing the complex
# DifficultSwenyly 29.08.2022 10:16
Quite difficult but I find it interesting and like fleeing the complex
# DifficultЕвгений 25.09.2022 04:29
Brain storm! But understood because of best the author

Language

Автор сайта

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

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

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

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

 

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

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



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



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

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal