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
. Она позволяет собрать несколько объектов одного типа в одно множество, где каждый объект предоставляет свой вклад. При добавлении объектов с @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
}
@BindsInstance
Используется для того, чтобы передать зависимость в компонент на этапе его создания. Она позволяет передавать значения в компонент, которые неизвестны на этапе компиляции, например, примитивы, строки или объекты, которые могут варьироваться во время выполнения.
// Здесь id передается при создании компонента и может быть использовано как зависимость в других классах.
@Component
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance id: Int): AppComponent
}
fun inject(repository: Repository)
}
Вопросы на собесе (18)
Component (5)
- Что такое Component в Dagger?
Интерфейс, который определяет зависимости, которые могут быть внедрены в другие классы. Он связывает модули с классами, нуждающимися в зависимостях.
- Что такое SubComponents?
Subcomponents в Dagger представляют собой вложенные компоненты, которые могут использовать зависимости родительского компонента. Они позволяют организовать более сложные иерархии зависимостей, улучшая модульность и повторное использование кода, а также обеспечивают управление временем жизни зависимостей на более детальном уровне.
- Разница между компонентом и сабкомпонентом в Dagger?
Компонент в Dagger является независимым и может сам предоставлять зависимости, тогда как сабкомпонент зависит от своего родительского компонента, автоматически наследуя его зависимости и область (
scope
).
- Что происходит внутри компонента при кодогенерации?
При кодогенерации Dagger создает классы-фабрики для предоставления зависимостей, граф зависимостей, обрабатывает аннотации и генерирует код для управления зависимостями, связывая модули и зависимости в соответствии с аннотациями
@Inject
,@Module
и@Component
.
- Знают ли сабкомпоненты друг о друге?
Сабкомпоненты не знают друг о друге напрямую; они зависят только от родительского компонента, через который могут косвенно получать общие зависимости.
- Что такое Component в Dagger?
Другие (13)
- Какие преимущества есть у Dagger?
Dagger обеспечивает compile-time внедрение зависимостей, что улучшает производительность, гарантирует безопасность типов и уменьшает объем кода, необходимого для конфигурации зависимостей.
- Как создать разные реализации одного типа?
Использовать
@Named
или@Qualifier
.
- Что такое Module в Dagger?
Класс, который предоставляет методы для создания зависимостей. Он определяет, как создать экземпляры объектов, которые будут предоставлены компонентам.
- Для чего нужна аннотация @Singleton?
Аннотация
@Singleton
в Dagger используется для обозначения, что класс или компонент должен иметь единственный экземпляр в пределах всего приложения. Это помогает управлять жизненным циклом объектов, обеспечивая их повторное использование и экономию ресурсов.
- Что такое multibinding?
Multibinding в Dagger позволяет связывать несколько зависимостей одного типа в коллекцию, такую как
Set
илиMap
. Это удобно для ситуаций, когда нужно инъектировать множество реализаций интерфейса или разные объекты, позволяя динамически добавлять или изменять зависимости без изменения основного кода.
- Зачем нужны скоупы в Dagger?
Скоупы в Dagger управляют временем жизни зависимостей. Они позволяют контролировать, как долго объекты будут существовать, обеспечивая их повторное использование в пределах определённого контекста (например,
Activity
или приложение), что помогает избежать избыточного создания объектов и утечек памяти.
- Какие базовые Scopes есть в Dagger?
•
@Singleton
для всего приложения.•
@ActivityScope
дляActivity
.•
@FragmentScope
дляFragment
.
- Чем @Binds отличается от @Provides?
@Binds
связывает интерфейс с реализацией без создания объекта и требует абстрактного метода, тогда как@Provides
создает и предоставляет экземпляры объектов, включая логику создания, и требует обычного метода.
- Что происходит с даггером, когда нажимаешь кнопку собрать проект?
При сборке создается граф зависимостей. Приложение получает доступ к графу через четко определенный набор корней.
- Как динамически добавлять зависимости в Dagger-компонент?
@BindsInstance
позволяет передавать экземпляры зависимостей непосредственно в компонент во время его создания.
- Какая аннотация заменена в данном примере на @SECRET_WORD?
interface Animal { fun makeSound() } class Cat: Animal { override fun makeSound() { println("Meow") } } @Module abstract class AnimalModule { @SECRET_WORD abstract fun bindAnimal(cat: Cat): Animal }
@Binds
- Для чего используется Scope @Singleton в Dagger2?
• Для изменения жизненного цикла зависимости внутри графа зависимостей.
• Для того, чтобы позволить di-компоненту пережить смерть
Activity
.• Для того, чтобы позволить зависимости внутри графа пережить смерть компонента.
- Какая аннотация в Dagger2 используется для инъекции переменных зависимостей, которые неизвестны на момент компиляции? например, для инъекции id:String в класс Repository?
•
@Named
•
@Binds
•
@AssistedInject
•
@Singleton
- Какие преимущества есть у Dagger?