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. Она позволяет собрать несколько объектов одного типа в одно множество, где каждый объект предоставляет свой вклад. При добавлении объектов с @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)
    1. Что такое Component в Dagger?

      Интерфейс, который определяет зависимости, которые могут быть внедрены в другие классы. Он связывает модули с классами, нуждающимися в зависимостях.

    1. Что такое SubComponents?

      Subcomponents в Dagger представляют собой вложенные компоненты, которые могут использовать зависимости родительского компонента. Они позволяют организовать более сложные иерархии зависимостей, улучшая модульность и повторное использование кода, а также обеспечивают управление временем жизни зависимостей на более детальном уровне.

    1. Разница между компонентом и сабкомпонентом в Dagger?

      Компонент в Dagger является независимым и может сам предоставлять зависимости, тогда как сабкомпонент зависит от своего родительского компонента, автоматически наследуя его зависимости и область (scope).

    1. Что происходит внутри компонента при кодогенерации?

      При кодогенерации Dagger создает классы-фабрики для предоставления зависимостей, граф зависимостей, обрабатывает аннотации и генерирует код для управления зависимостями, связывая модули и зависимости в соответствии с аннотациями @Inject, @Module и @Component.

    1. Знают ли сабкомпоненты друг о друге?

      Сабкомпоненты не знают друг о друге напрямую; они зависят только от родительского компонента, через который могут косвенно получать общие зависимости.

  • Другие (13)
    1. Какие преимущества есть у Dagger?

      Dagger обеспечивает compile-time внедрение зависимостей, что улучшает производительность, гарантирует безопасность типов и уменьшает объем кода, необходимого для конфигурации зависимостей.

    1. Как создать разные реализации одного типа?

      Использовать @Named или @Qualifier.

    1. Что такое Module в Dagger?

      Класс, который предоставляет методы для создания зависимостей. Он определяет, как создать экземпляры объектов, которые будут предоставлены компонентам.

    1. Для чего нужна аннотация @Singleton?

      Аннотация @Singleton в Dagger используется для обозначения, что класс или компонент должен иметь единственный экземпляр в пределах всего приложения. Это помогает управлять жизненным циклом объектов, обеспечивая их повторное использование и экономию ресурсов.

    1. Что такое multibinding?

      Multibinding в Dagger позволяет связывать несколько зависимостей одного типа в коллекцию, такую как Set или Map. Это удобно для ситуаций, когда нужно инъектировать множество реализаций интерфейса или разные объекты, позволяя динамически добавлять или изменять зависимости без изменения основного кода.

    1. Зачем нужны скоупы в Dagger?

      Скоупы в Dagger управляют временем жизни зависимостей. Они позволяют контролировать, как долго объекты будут существовать, обеспечивая их повторное использование в пределах определённого контекста (например, Activity или приложение), что помогает избежать избыточного создания объектов и утечек памяти.

    1. Какие базовые Scopes есть в Dagger?

      @Singleton для всего приложения.

      @ActivityScope для Activity.

      @FragmentScope для Fragment.

    1. Чем @Binds отличается от @Provides?

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

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

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

    1. Как динамически добавлять зависимости в Dagger-компонент?

      @BindsInstance позволяет передавать экземпляры зависимостей непосредственно в компонент во время его создания.

    1. Какая аннотация заменена в данном примере на @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

    1. Для чего используется Scope @Singleton в Dagger2?

      Для изменения жизненного цикла зависимости внутри графа зависимостей.

      Для того, чтобы позволить di-компоненту пережить смерть Activity.

      Для того, чтобы позволить зависимости внутри графа пережить смерть компонента.

    1. Какая аннотация в Dagger2 используется для инъекции переменных зависимостей, которые неизвестны на момент компиляции? например, для инъекции id:String в класс Repository?

      @Named

      @Binds

      @AssistedInject

      @Singleton