Dagger HILT с Kotlin и Jetpack Compose
Честно говоря — когда я впервые услышал про внедрение зависимостей в Android, я подумал: «отлично, ещё одна вещь, которая усложнит мою жизнь». Android-разработка и без того порой кажется непосильной, а DI выглядел как очередное препятствие. Но поработав с Dagger HILT какое-то время, я понял, что он на самом деле упрощает жизнь. Давайте разберём это вместе.
Что вообще такое зависимость?
Прежде чем погружаться в сложности, давайте разберёмся с этим. Зависимость — это просто... то, что нужно вашей функции для работы. Вот и всё. Смотрите:
@Composable
fun MainScreen(viewModel: MyViewModel) {
// ваш UI-код
}
Видите этот viewModel? Это и есть зависимость. MainScreen не может работать без него.
Всё просто.
Почему нельзя просто передавать всё вручную?
Можно! И для небольших проектов это вполне нормально. Но представьте, что у вас 50 функций, и все они нуждаются в одном экземпляре базы данных или API-клиента. А теперь представьте, что вы решили перейти с Retrofit на Ktor. Приятного обновления всех 50 функций!
Вот здесь внедрение зависимостей и спасает вашу голову. Вы один раз описываете, как создавать объекты, а HILT берёт всё остальное на себя. Нужна та же база данных в 20 разных местах? Не проблема. Хотите один и тот же экземпляр везде? Легко.
Что такое Dagger HILT?
HILT — это ваш личный помощник по управлению зависимостями. Это библиотека, которая автоматически создаёт и предоставляет нужные объекты тогда, когда они нужны. Хотите один общий экземпляр на весь app? HILT справится. Нужно что-то, что живёт ровно столько, сколько Activity? HILT умеет и это.
Давайте запачкаем руки
Шаг 1: Добавление зависимостей
Для начала нужно добавить HILT в проект. Я использую KSP, потому что он быстрее KAPT, но вы можете использовать любой из них.
В build.gradle на уровне модуля app:
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
ksp("com.google.dagger:hilt-android-compiler:2.48")
ksp("androidx.hilt:hilt-compiler:1.2.0")
}
plugins {
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
В build.gradle на уровне проекта:
plugins {
id("com.google.dagger.hilt.android") version "2.48" apply false
id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}
Шаг 2: Создание модулей
В модулях вы говорите HILT, как создавать ваши объекты. Думайте о них как о книгах рецептов. Создайте пакет «di» (сокращение от dependency injection) и добавьте в него объект AppModule:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
// Здесь будут ваши «рецепты» зависимостей
}
@Module сообщает HILT: «здесь я описываю свои зависимости».
@InstallIn определяет область видимости — как долго эти объекты должны жить.
Понимание областей видимости
- SingletonComponent::class — один экземпляр на всё время жизни приложения
- ActivityComponent::class — живёт столько, сколько живёт Activity
Шаг 3: Предоставление зависимостей
Теперь напишем реальный код. Допустим, вам нужна база данных Room:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(
@ApplicationContext context: Context
): TaskDatabase {
return Room.databaseBuilder(
context,
TaskDatabase::class.java,
"task_database"
).build()
}
}
@ApplicationContext — очень удобная штука: HILT автоматически передаёт вам
контекст приложения, и вам не нужно таскать его по всему коду вручную.
Ещё один пример — с Retrofit:
@Provides
@Singleton
fun provideApi(): MyAPI {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MyAPI::class.java)
}
Шаг 4: Внедрение в ViewModels
ViewModels работают немного иначе. Вы аннотируете их через @HiltViewModel
и используете @Inject constructor():
@HiltViewModel
class TaskViewModel @Inject constructor(
private val taskRepository: TaskRepository,
private val context: Context
) : ViewModel() {
// Логика вашего ViewModel
}
Всё, что вы укажете в конструкторе, должно быть предоставлено в AppModule. HILT автоматически свяжет всё воедино.
Шаг 5: Настройка класса Application
Создайте новый файл (я обычно называю его MyApp.kt):
@HiltAndroidApp
class MyApp : Application() {
// Вот и всё! HILT сделает всё остальное
}
Затем добавьте его в AndroidManifest.xml:
<application
android:name=".MyApp"
android:allowBackup="true"
...>
Шаг 6: Аннотирование MainActivity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Ваш код
}
}
Шаг 7: Использование зависимостей
Если вам нужно внедрить что-то напрямую в Activity (хотя чаще всего вы будете использовать ViewModels), сделать это можно так:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var taskViewModel: TaskViewModel
@Inject
lateinit var categoryViewModel: CategoryViewModel
}
Моё честное мнение
Я понимаю, что поначалу это выглядит громоздко. Когда я только начинал, я думал: «зачем нельзя просто создавать объекты обычным способом?». Но как только вы поработаете над реальным проектом с несколькими экранами, базами данных, API-запросами и всем остальным — вы по достоинству оцените HILT.
Лучший способ научиться — просто использовать его. Начните с малого: внедрите базу данных или простой репозиторий. Как только увидите, как это работает, вам будет сложно представить, как вы жили без этого.
Поверьте, будущий вы скажет спасибо нынешнему вам за то, что разобрались с этим сейчас.
Список статей