Hilt
Dagger
Генерирует зависимости во время сборки приложения. Если приложение большое - отжирается много времени у сборки. Очень быстрый в runtime.
• Внедрение зависимостей. Позволяет автоматически внедрять зависимости в объекты, уменьшая связанность кода и делая его более гибким и легким для тестирования. Зависимости могут быть классами, интерфейсами, контекстами и другими объектами, которые нужны для работы приложения.
• Управление жизненным циклом зависимостей: Платформа предоставляет механизмы для управления временем жизни объектов, которые используются в приложении. Это позволяет эффективно использовать ресурсы и уменьшить потребление памяти.
• Создание синглтонов и одноразовых зависимостей: Фреймворк позволяет легко определить, что объекты должны быть синглтонами (единственными экземплярами) или одноразовыми (создаются новые экземпляры при каждом запросе).
• Обработка сложных зависимостей: Если ваше приложение имеет сложные зависимости, Dagger может автоматически разрешить их и обеспечить, чтобы объекты правильно создавались и использовались.
• Модульное тестирование: Dagger позволяет легко заменять реальные зависимости на фиктивные (mock) или подставные (stub) объекты при модульном тестировании. Это упрощает и ускоряет процесс тестирования приложения.
• Упрощение конфигурации: Dagger устраняет необходимость вручную создавать и настраивать зависимости, что снижает сложность кода и упрощает его поддержку.
• Уменьшение дублирования кода: Dagger позволяет определить глобальные зависимости, которые могут быть использованы в различных частях приложения, что уменьшает дублирование кода.
• Повышение читаемости кода: Использование Dagger делает зависимости явными и понятными, что повышает читаемость кода и облегчает его анализ и понимание.
Hilt
Dagger с аннотациями.
• валидация графа в момент компиляции. Если приложение запустилось, то как минимум всех зависимостей хватает.
• кодогенерация замедляющая время сборки.
@HiltAndroidApp | Запускает генерацию кода Hilt. Базовый класс приложения служит контейнером зависимостей на уровне приложения. |
@AndroidEntryPoint | Создает отдельный компонент Hilt для класса и предоставляет для него зависимости. Поддерживает: Activity, Fragment, View, Service, BroadcastReceiver, ViewModel. |
@HiltViewModel | Предоставляет зависимости для ViewModel. |
@Module | Информирует Hilt о том, как предоставлять зависимости определенных типов. Устанавливается в компонент. |
@InstallIn | Информирует в каком классе Android будет использоваться или устанавливаться каждый модуль. SingletonComponent (Application), ViewModelComponent (ViewModel), ActivityComponent (Activity), FragmentComponent (Fragment), ViewComponent (View), ServiceComponent (Service). |
@Binds | Сообщает Hilt какую реализацию использовать, когда ему нужно предоставить реализацию интерфейса. |
@Provide | Сообщает Hilt о том, как предоставлять экземпляры этого типа. Внедрение зависимостей в конструктор невозможно, если класс получен из внешней библиотеки (Room, Retrofit). |
@Qualifier | Позволяет предоставлять различные реализации одного и того же типа в качестве зависимостей. |
@ApplicationContext | Переопределенный квалификатор контекста приложения. |
@ActivityContext | Переопределенный квалификатор контекста активити. |
Scopes
Это механизм для управления временем жизни объектов, создаваемых в графе зависимостей. Они позволяют контролировать, как и когда будет создаваться и повторно использоваться объект, предоставленный в определенном контексте.
• Контроль времени жизни объектов: Скоупы позволяют создать объект один раз и повторно использовать его в течение определенного времени (например, пока живет Activity или приложение).
• Оптимизация ресурсов: Вместо того, чтобы каждый раз создавать новый объект, вы можете использовать один и тот же экземпляр там, где это уместно.
• Избежание утечек памяти: Правильное использование скоупов помогает предотвратить утечки памяти, контролируя время жизни объектов.
Android class | Generated component | Scope |
---|---|---|
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
View annotated with @WithFragmentBindings | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
Lazy Initialization
Provider
Интерфейс, который позволяет отложить создание объекта и при необходимости создавать его каждый раз заново. Provider используется для управления созданием зависимостей вручную, когда нужно иметь доступ к зависимости каждый раз, как она требуется, а не использовать один и тот же экземпляр.
• Ленивая инициализация: Provider позволяет создать объект только тогда, когда он действительно потребуется, а не в момент создания графа зависимостей.
• Создание объектов по запросу: В отличие от обычной инъекции, при использовании Provider зависимость будет создаваться каждый раз при вызове get.
• Множественные вызовы: Каждый вызов метода get возвращает новый экземпляр зависимости, если скоуп не ограничивает это.
class SomeClass @Inject constructor(
private val someDependencyProvider: Provider<SomeDependency>
) {
fun useDependency() {
val dependency = someDependencyProvider.get() // Создаст или вернет объект SomeDependency
// Работа с dependency
}
}
Lazy
Интерфейс, который откладывает создание объекта до момента, когда он впервые потребуется, и гарантирует, что этот объект будет создан только один раз.
• Ленивая инициализация: Объект создается только при первом обращении к нему через метод get.
• Один и тот же экземпляр: После создания объект кешируется, и при последующих вызовах будет возвращаться один и тот же экземпляр.
• Оптимизация производительности: Использование Lazy позволяет откладывать создание объекта до тех пор, пока он реально не понадобится, что может быть полезно для тяжелых зависимостей.
class SomeClass @Inject constructor(
private val someDependencyLazy: Lazy<SomeDependency>
) {
fun useDependency() {
val dependency = someDependencyLazy.get() // Объект создается при первом вызове
// Работа с dependency
}
}
@Named
Используется для различения зависимостей одного типа при инъекции. Это позволяет указать конкретную зависимость через строковый идентификатор, если существует несколько реализаций одного и того же типа.
@Module
class NetworkModule {
@Provides
@Named("baseUrl")
fun provideBaseUrl(): String = "https://api.example.com"
@Provides
@Named("apiKey")
fun provideApiKey(): String = "1234567890"
}
class NetworkService @Inject constructor(
@Named("baseUrl") private val baseUrl: String,
@Named("apiKey") private val apiKey: String
)
@Qualifier
Используется для создания пользовательских аннотаций, которые позволяют различать зависимости одного типа. Это более гибкий и расширяемый способ, чем использование @Named
, позволяющий избежать ошибок, связанных с использованием строковых идентификаторов.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BaseUrl
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApiKey
@Module
class NetworkModule {
@Provides
@BaseUrl
fun provideBaseUrl(): String = "https://api.example.com"
@Provides
@ApiKey
fun provideApiKey(): String = "1234567890"
}
class NetworkService @Inject constructor(
@BaseUrl private val baseUrl: String,
@ApiKey private val apiKey: String
)
@IntoSet
Используется для добавления зависимости в Set. Она позволяет собрать несколько объектов одного типа в одно множество (Set), где каждый объект предоставляет свой вклад. При добавлении объектов с @IntoSet, Dagger автоматически создает и управляет множеством, собирая все помеченные зависимости одного типа.
@Module
class AnimalModule {
@Provides
@IntoSet
fun provideDog(): String = "Dog"
@Provides
@IntoSet
fun provideCat(): String = "Cat"
}
class AnimalService @Inject constructor(
private val animals: Set<String>
) {
fun printAnimals() {
animals.forEach { println(it) }
}
}
@IntoMap
Используется для добавления зависимостей в Map. Она позволяет собрать несколько объектов с различными ключами в одну карту (Map<Key, Value>), где каждый объект привязывается к определенному ключу. С помощью аннотации @IntoMap и соответствующего ключа можно связать объект с определенным значением ключа в мапе.
@Module
class AnimalModule {
@Provides
@IntoMap
@StringKey("dog")
fun provideDog(): String = "Bark"
@Provides
@IntoMap
@StringKey("cat")
fun provideCat(): String = "Meow"
}
class AnimalService @Inject constructor(
private val animals: Map<String, String>
) {
fun printSounds() {
animals.forEach { (animal, sound) ->
println("$animal makes $sound")
}
}
}
@StringKey
Задает ключ для объекта.
@Subcomponent
Аннотация, используемая для создания вложенных компонентов, которые могут унаследовать зависимости от родительского компонента. Она позволяет организовать иерархию компонентов и управлять скоупами зависимостей для отдельных частей приложения, таких как Activity, Fragment или другие логические блоки. @Subcomponent объявляет новый компонент, который может наследовать зависимости от своего родительского компонента.
• Наследование зависимостей: Subcomponent может использовать все зависимости родительского компонента.
• Изоляция зависимостей: Subcomponent может иметь свои собственные зависимости, которые доступны только внутри этого компонента.
• Скоупы: Каждый Subcomponent может иметь свои скоупы, что полезно для управления временем жизни объектов (например, @ActivityScope).
@Subcomponent
interface LoginComponent {
// Определяем метод для инъекции зависимостей в LoginActivity
fun inject(loginActivity: LoginActivity)
// Builder для создания LoginComponent
@Subcomponent.Builder
interface Builder {
fun build(): LoginComponent
}
}
// Связь с родительским компонентом
@Component(modules = [AppModule::class])
interface AppComponent {
// Метод для создания Subcomponent
fun loginComponent(): LoginComponent.Builder
}
// Использование Subcomponent
class LoginActivity: AppCompatActivity() {
@Inject
lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Получение экземпляра Subcomponent через родительский компонент
(application as MyApp).appComponent
.loginComponent()
.build()
.inject(this)
}
}
@Provides
Используется в Dagger для предоставления объектов, которые требуют специальной логики создания. Используется для указания метода, который должен предоставлять (создавать) экземпляр зависимости. Она применяется внутри модуля (класса, отмеченного аннотацией @Module) и позволяет описать логику создания объекта, который не может быть создан с помощью конструктора или требует дополнительной настройки. Метод, отмеченный @Provides, должен находиться в классе, помеченном @Module, и возвращать экземпляр зависимости, который будет добавлен в граф зависимостей Dagger.
• Модули и методы с @Provides: Модуль (@Module) содержит методы с аннотацией @Provides, которые описывают, как создавать зависимости.
• Взаимодействие зависимостей: В методе @Provides могут использоваться другие зависимости, которые Dagger автоматически передаст как параметры (например, OkHttpClient в методе provideApiService).
• Возвращаемый объект: Метод должен возвращать объект, который требуется предоставить в графе зависимостей.
@Module
class NetworkModule {
@Provides
fun provideHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
fun provideApiService(client: OkHttpClient): ApiService {
return ApiService(client)
}
}
@Binds
Используется для связывания (приведения) интерфейса к его реализации. В отличие от @Provides, которая требует написания метода с логикой создания объекта, @Binds позволяет указать, что конкретная реализация интерфейса должна быть использована в графе зависимостей. Метод, отмеченный @Binds, должен находиться в классе, помеченном аннотацией @Module. Этот метод принимает объект реализации и возвращает его в виде интерфейса.
• Модули и методы с @Binds: В модуле (@Module) используется метод с @Binds для связывания интерфейса с конкретной реализацией. Метод должен быть абстрактным и возвращать интерфейс, а параметр метода — реализацию.
• Интерфейс и реализация: Метод с @Binds указывает Dagger, что при запросе зависимости типа интерфейса ApiService следует использовать реализацию ApiServiceImpl.
• Краткость: @Binds используется, когда реализация создается и управляется другим способом, а метод просто связывает интерфейс с реализацией без дополнительной логики создания.
• Чистота кода: Упрощает модуль, избегая избыточного кода создания объектов и конфигурации.
• Скорость: Менее затратный способ связывания зависимостей по сравнению с @Provides.
interface ApiService {
fun getData(): String
}
class ApiServiceImpl: ApiService {
override fun getData(): String {
return "Data from API"
}
}
@Module
abstract class ApiModule {
@Binds
abstract fun bindApiService(impl: ApiServiceImpl): ApiService
}
Hilt. Вопросы на собесе
- Как создать разные реализации одного типа?
- Что такое Component и Module?
- Для чего нужна аннотация @Singleton?
- Что такое multibinding? Какие аннотации? Когда полезно использовать?
- Что такое subcomponents?
- Скоупы в Dagger что это и зачем они нужны?
- Чем @Binds отличается от @Provides?
@Binds
связывает интерфейс с реализацией без создания объекта и требует абстрактного метода, тогда как@Provides
создает и предоставляет экземпляры объектов, включая логику создания, и требует обычного метода
- Что происходит с даггером, когда нажимаешь кнопку собрать проект?
При сборке создается граф зависимостей. Приложение получает доступ к графу через четко определенный набор корней.