В прошлых уроках мы подробно разобрались с простым случаем, когда в проекте два модуля. Давайте добавлять новые модули, чтобы поработать с более сложными сценариями.
Оглавление
4. Используем Context при создании объекта
6. Фрагмент в отдельный модуль
10. Заключение и полезные ссылки
Мы решаем еще раз поменять реализацию Database. И теперь нам для базы данных понадобилась работа с файлами.
Под это мы создаем новый модуль core, и в нем - новый класс FileManager.
FileManager.kt (:core)
class FileManager { }
А также добавляем даггер-модуль, который будет отвечать за создание объекта FileManager:
CoreModule.kt (:core)
@Module class CoreModule { @Provides fun provideFileManager() = FileManager() }
Попробуем использовать FileManager в классе Database:
Database.kt (:data)
class Database( private val context: Context, private val fileManager: FileManager ) { }
Такой код выдаст ошибку, т.к. модуль data не знает про core, а значит классы модуля data не знают про классы модуля core.
Схема модулей:
Чтобы это исправить, пропишем зависимость data от core
build.gradle (:data)
implementation project(path: ':core')
Схема модулей:
Теперь класс Database из модуля data увидит и сможет использовать класс FileManager из модуля core.
Подправим даггер-модуль DataModule (он теперь тоже видит и может работать с FileManager):
DataModule (:data)
@Module class DataModule { @Provides fun provideDatabase(context: Context, fileManager: FileManager) = Database(context, fileManager) }
Метод provideDatabase получает FileManager и использует его, чтобы создать Database.
Схема объектов:
Чтобы разгрузить эту схему, я убрал из нее даггер-модули.
На первый взгляд все ок. TasksActivity (из app) использует Database (из data), который использует FileManager (из core).
Но в этой схеме есть проблема, которая может быть не сразу заметна. Чтобы явно ее увидеть, давайте попробуем создать объект Database в нашем компоненте:
MyAppComponent (:app)
Чтобы компонент смог создать Database, он должен в метод provideDatabase передать объект FileManager. Но в модуле app мы ничего не знаем про FileManager, потому что модуль app не знает про core.
Это важный для понимания момент. Если app знает про data, а data знает про core, это не значит, что app знает про core.
Это правило действует, если мы используем implementation, когда объявляем зависимости модулей. Но есть и другие варианты. Можно зависимость модуля data от модуля core оформить так:
build.gradle (:data)
api project(path: ':core')
Вместо implementation мы можем использовать api. В этом случае все модули, знающие про модуль data, автоматически будут знать и про модуль core. Но это тема для отдельной статьи, там свои плюсы, минусы и подводные камни. Я буду использовать только implementation зависимости.
Чтобы компонент смог работать с FileManager, нам надо добавлять зависимость app от core:
build.gradle (:app)
implementation project(path: ':core')
Схема модулей:
Добавилась зависимость app от core.
Теперь мы можем в интерфейс компонента добавить CoreModule:
AppComponent (:app)
@Component(modules = [DataModule::class, CoreModule::class]) interface AppComponent { fun injectTasksActivity(tasksActivity: TasksActivity) }
Компонент даггера будет знать, где ему взять FileManager
Наш компонент тоже надо этому научить:
MyAppComponent (:app)
class MyAppComponent(private val context: Context): AppComponent { private val dataModule = DataModule() private val coreModule = CoreModule() override fun injectTasksActivity(tasksActivity: TasksActivity) { tasksActivity.database = dataModule.provideDatabase(context, coreModule.provideFileManager()) } }
Используем CoreModule для получения FileManager и создаем Database.
Результат
Мы создали новый модуль core с объектом FileManager, который нужен при создании Database. Очевидно, что при этом нам пришлось добавить зависимость data от core.
Но не совсем очевидно, почему нам пришлось добавить еще и зависимость app от core. Ведь TasksActivity не надо было ничего знать про FileManager, ему хватало Database.
Схема объектов:
На схеме видно, что компонент использует и класс Database из data и класс FileManager из core, чтобы создать объект Database. Поэтому app у нас должен знать про оба этих модуля.
Возможно, возникает мысль, что DataModule мог бы сам использовать CoreModule, чтобы создать FileManager. Но так это не работает. Даггер-модули не используют друг друга. Компонент - это единый центр, который использует все доступные ему даггер-модули, чтобы создавать объекты.
В итоге из-за компонента нам пришлось добавить зависимость модуля app от core. А вот если бы TasksActivity у нас находилось в отдельном модуле, то этому модулю хватило бы зависимости только от модуля data.
В следующем уроке попробуем вынести TasksActivity в отдельный модуль.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня