Hilt

https://developer.android.com/training/dependency-injection
https://developer.android.com/training/dependency-injection/manual
https://developer.android.com/training/dependency-injection/hilt-android
https://developer.android.com/training/dependency-injection/hilt-multi-module
https://developer.android.com/training/dependency-injection/hilt-jetpack
https://developer.android.com/training/dependency-injection/hilt-testing
https://developer.android.com/training/dependency-injection/hilt-cheatsheet
https://developer.android.com/training/dependency-injection/dagger-basics
https://developer.android.com/training/dependency-injection/dagger-android
https://developer.android.com/training/dependency-injection/dagger-multi-module
03.03.2021https://habr.com/ru/companies/otus/articles/545222/
09.12.2020https://habr.com/ru/companies/otus/articles/532066/
06.02.2017https://habr.com/ru/articles/320676/
23.03.2016https://habr.com/ru/articles/279641/
14.03.2016https://habr.com/ru/articles/279125/
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 classGenerated componentScope
ApplicationSingletonComponent@Singleton
ActivityActivityRetainedComponent@ActivityRetainedScoped
ViewModelViewModelComponent@ViewModelScoped
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
View annotated with @WithFragmentBindingsViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@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. Вопросы на собесе
  1. Как создать разные реализации одного типа?
  1. Что такое Component и Module?
  1. Для чего нужна аннотация @Singleton?
  1. Что такое multibinding? Какие аннотации? Когда полезно использовать?
  1. Что такое subcomponents?
  1. Скоупы в Dagger что это и зачем они нужны?
  1. Чем @Binds отличается от @Provides?

    @Binds связывает интерфейс с реализацией без создания объекта и требует абстрактного метода, тогда как @Provides создает и предоставляет экземпляры объектов, включая логику создания, и требует обычного метода

  1. Что происходит с даггером, когда нажимаешь кнопку собрать проект?

    При сборке создается граф зависимостей. Приложение получает доступ к графу через четко определенный набор корней.