На прошлом уроке мы создали простое приложение с двумя модулями app и data, и добавили зависимость между ними. Теперь мы в TasksActivity можем создать и использовать Database.
Это все хорошо, но мы хотим Database не руками создавать, а инджектить даггером. Давайте подключать его к проекту.
Оглавление
4. Используем Context при создании объекта
6. Фрагмент в отдельный модуль
10. Заключение и полезные ссылки
Подключение даггера
В обоих модулях в файлах build.gradle добавляем:
build.gradle (:app, :data)
plugins { id 'kotlin-kapt' } dependencies { implementation 'com.google.dagger:dagger:2.42' kapt 'com.google.dagger:dagger-compiler:2.42' }
В последующих создаваемых модулях не забывайте делать те же действия.
Чтобы даггер мог предоставлять объекты, нам нужно создать компонент и даггер-модуль (так я буду далее по тексту называть даггеровский @Module, чтобы не путать с модулем проекта).
В мультимодульном проекте возникает вопрос: в каком модуле проекта создавать даггер-модули и компоненты?
Даггер-модуль
Даггер-модуль имеет смысл создавать как можно ближе к объектам, которые он будет провайдить. В нашем случае мы создаем его там, где лежит Database - в модуле data.
Во-первых, это удобно и интуитивно понятно, когда даггер-модуль и создаваемый объект находятся рядом - не надо будет бегать по проекту при каких то изменениях в создании объекта.
Во-вторых, когда даггер-модуль и Database находятся в одном модуле, они имеют одинаковые знания о других классах/модулях проекта. И если нам при создании Database будет нужен еще какой-то объект из какого-то модуля, то наш даггер-модуль точно будет знать/видеть этот объект.
В модуле data создаем даггер-модуль, который отвечает за создание Database:
DataModule (:data)
@Module class DataModule { @Provides fun provideDatabase() = Database() }
Компонент
Переходим к компоненту. В каком модуле он должен находиться?
Давайте исходить из задач компонента. Он создает объекты и инджектит их в Activity. Для этого компонент должен находиться в модуле, из которого видны:
- Activity, в которое инджектим
- создаваемые объекты, которые инджектим
Модуль data тут явно не подходит, т.к. он знает только про свое содержимое, а про TasksAcivity из модуля app не знает. Т.е. если компонент находится в модуле data, то он сможет создать объект Database, но не сможет заинджектить его, потому что не будет знать про класс TasksActivity.
А вот модуль app для компонента вполне годится. Этот модуль знает и про объекты из модуля data (т.к. мы прописали dependencies) и про TasksActivity, куда надо инджектить. В модуле app компонент будет иметь доступ ко всем необходимым объектам.
Кроме того, есть рекомендация от гугла: компонент надо создавать в том модуле, где находится объект, куда компонент будет инджектить. Мы инджектим в TasksAcivity. Оно находится в модуле app. Значит и компонент должен быть в этом модуле.
Отдельный компонент под TasksActivity пока создавать не будем. Ограничимся созданием общего App компонента, который будет уметь инджектить в TasksActivity:
AppComponent (:app)
@Component(modules = [DataModule::class]) interface AppComponent { fun injectTasksActivity(tasksActivity: TasksActivity) }
Не забываем предоставить компоненту даггер-модуль DataModule, чтобы компонент умел создавать Database
Инджект
Осталось создать App класс, который будет создавать и держать компонент:
App (:app)
class App: Application() { lateinit var appComponent: AppComponent override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.create() } }
Не забудьте добавить App класс в манифест
Если класс DaggerAppComponent не создается, то включайте стандартную магию: Clean Project, Rebuild Project, Invalidate Caches и вот это вот все.
Все готово. Теперь мы можем использовать компонент, чтобы инджектить Database в TasksActivity:
TasksActivity (:app)
class TasksActivity : AppCompatActivity() { @Inject lateinit var database: Database override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_tasks) (applicationContext as App).appComponent.injectTasksActivity(this) } }
Получаем класс App, берем из него компонент и выполняем инджект.
Схема получилась такая:
Сплошные стрелки показывают, что где используется. TasksActivity использует Database и компонент.
А пунктирными стрелками я показал путь Database до компонента. Компонент использует DataModule, который создает Database.
Результат
Мы подключили даггер к проекту и выяснили, где лучше создавать даггер-модуль и компонент.
Даггер-модуль создаем там, где лежит создаваемый объект. В нашем примере это модуль data, где лежит Database.
Компонент создаем там, где лежит объект, в который будет выполняться инджект. В нашем примере это модуль app, где лежит TasksActivity.
Это достаточно важный момент для понимания темы. Компонент и даггер-модули не обязаны находиться в одном модуле. Компонент может использовать даггер-модули из других модулей проекта. Главное, чтобы все зависимости были прописаны.
Кому-то тут все будет понятно и прозрачно. Но для других компонент до сих пор выглядит, как черный ящик, который творит магию.
Давайте откроем этот ящик. На следующем уроке мы создадим свою реализацию компонента AppComponent и используем ее вместо даггеровской. Так мы будем максимально понимать, что происходит под капотом.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Комментарии
RSS лента комментариев этой записи