Dagger - makstron/info GitHub Wiki

Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android.

Setup

apply plugin: 'kotlin-kapt'
dependencies {
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
    implementation "com.google.dagger:dagger:$dagger_version"
}

Android extension

dependencies {
    kapt "com.google.dagger:dagger-android-processor:$dagger_version"
    implementation "com.google.dagger:dagger-android:$dagger_version"
}
ext {
    dagger_version = '2.28.3'
}

@Inject

Существует несколько способов запроса зависимости

  1. Внедрение в конструктор класса
  2. Если один из конструкторов класса помечен этой аннотацией, то компонент использует этот конструктор, чтобы создать объект без участия модулей.
//тут можно поместить @Scope зависимости
public class ManagerA{
  @Inject
  public ManagerA(Context context){ /* */}
}
  1. Внедрение через метод. Метод будет выполнен после вызова конструктора
@Inject
public void register(SomeDepends depends){
  depends.register(this);
}
  1. Внедрение в поля класса. Поля должны быть не приватными и не финальными
@Inject ManagerB managerB;
  1. Вызов геттера необходимого нам объекта. Этот геттер также используется для связывания нескольких графов зависимостей.
managerA = component.getManagerA();

@Module

Модуль – это фабрика объектов, разрешающая наши зависимости (это классы, способные создавать экземпляры определенных классов).

Модуль должен быть помечен аннотацией @Module, а методы, генерирующие зависимости, – @Provides. И если необходимо отметить область видимости, то модуль помечаем одной из аннотаций @Scope.
Имена методов значения не имеют: Dagger смотрит лишь на сигнатуры.
Аннотация @Module может содержать в себе другие модули. Таким образом зависимости, содержащиеся в них, будут доступны в ссылающемся на них модуле.

@Module(includes={SomeModule1.class, SomeModule2.class})
public class YahooWeatherModule {
    @Provides
    WeatherService provideWeatherService() {
        return new YahooWeather();
    }
}

Модуль может содержать конструктор с параметром, если для разрешения зависимостей ему нужны данные извне.

@Module
class AppModule{
  Application app;
  AppModule(App app){
    this.app = app;
  }
  @PerApplication
  @Provides
  Context provideContext(){return app;}
}

@Provide

Методы внутри @Module, “говорящие Dagger, как мы хотим сконструировать и предоставить зависимость

@Component

Компонент является связующим звеном между модулями и просителями зависимостей.

Отдать зависимость можно

  • через метод компонента (в который будет передан объект, запрашивающий зависимости)
  • через геттер (который вернёт зависимость).
    В одном компоненте могут быть как методы, так и геттеры. Названия методов или геттеров не важны.

В обоих случаях мы сначала создаём интерфейс и помечаем его аннотацией @Component или @Subcomponent. Далее указываем, как будут разрешаться зависимости. Необходимо добавить список модулей, которые будут генерировать зависимости.

@Component(modules={AppModule.class})
interface AppComponent{
  void injectInto(App holder);
  ManagerA getManagerA ();
}

@Component.Builder

Builder - интерфейс или абстрактный клас внутри компонента помеченый анотацией Builder. Dagger генерирует класс билдер для компонента или сабКомпонента @SubComponent.Builder.
Билдер должен содержать один метод с возвращаемым типом компонента(AppComponent).
Остальные методы должны возвращать Builder

@Component.Builder
interface Builder {

    @BindsInstance
    fun app(app: App): Builder

    fun build(): AppComponent
}
DaggerAppComponent
        .builder()
        .app(app)
        .build()

@BindsInstance

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

Если принимается одаковый тип в двух @BindsInstance то можно добавить квалификаторы

@BindsInstance
fun app(@HostUrl url: String): Builder
fun app(@AppId appId: String): Builder

@SubComponent

Subcomponents - Use this when you want to keep two components coupled.

Сабкомпоненты - это просто компоненты, которые наследуют и расширяют родительский компонент. Т.е. кроме объектов в своих модулях, они видят и все объекты из модулей родительского компонента.

Особенности:

  • Необходимо прописывать в интерфейсе родителя метод получения Сабкомпонента
  • Для Сабкомпонента доступны все объекты родителя
  • Родитель может быть только один

Разработчик сам контролирует время жизни сабкомпонента (пока на него есть ссылки)

@Subcomponent(modules={MailModule.class})
public interface MailComponent {
 
}

Метод создания сабкомпонента описывается в родительском компоненте

@Component(modules = {AppModule.class})
public interface AppComponent {
 
    MailComponent createMailComponent();
 
}

А реализация метода остается за даггером.

Component dependencies

Component dependencies - Use this when you want to keep two components independent.

Особенности Component dependencies:

  • Два зависимых компонента не могут иметь одинаковый scope.
  • Родительский компонент в своем интерфейсе должен явно задавать объекты, которыми могут пользоваться зависимые компоненты.
  • Компонент может зависеть от нескольких компонент.
//parent component
@Component(modules = [])
interface AppComponent {
    fun context(): Context;
    fun provideUtils(): Utils;
}

//dependent component
@Component(dependencies = [AppComponent::class] ,modules = [])
interface FeatureComponent {
    // context - allowed
    // provideUtils - allowed

    fun context(): Context;
}

//dependent component
@Component(dependencies = [FeatureComponent ::class] ,modules = [])
interface FeatureComponent {
    // only context - allowed (only context has getter in parent component)
}

Для того чтобы получить дочерний компонент нужно передать ему его зависимости (родительский компонент)

val appComponent: AppComponent = DaggerAppComponent.builder().build()

val featureComponent: FeatureComponent = DaggerFeatureComponent.builder()
                                                               .appComponent(appComponent)
                                                               .build()

Lazy

Ленивое создание объекта

Оборачиваем NetworkUtils в Lazy

import dagger.Lazy

class MainActivity : AppCompatActivity() {

   lateinit var networkUtilsLazy: Lazy<NetworkUtils>

   override fun onCreate(savedInstanceState: Bundle?) {
       networkUtilsLazy = (application as App).appComponent.getNetworkUtils()
       
       //створить або віддасть раніше створений networkUtils
       val networkUtils = networkUtilsLazy.get()
   }
}

На стороне компонента

@Component(modules = [...])
interface AppComponent {
   fun getNetworkUtils(): NetworkUtils
}

Provider

При каждом вызове get будет создавать новый объект

import javax.inject.Provider

class MainActivity : AppCompatActivity() {

   lateinit var networkUtils: Provider<NetworkUtils>

   override fun onCreate(savedInstanceState: Bundle?) {
       networkUtilsLazy = (application as App).appComponent.getNetworkUtils()
       
       //створюватиме networkUtils щоразу
       val networkUtils = networkUtilsProvider.get()
   }
}

На стороне компонента

@Component(modules = [...])
interface AppComponent {
   fun getNetworkUtils(): NetworkUtils
}

@Named

Если необходимо провайдить несколько объектов одного типа

@Module
public class AppModule {

    @Provides
    @Singleton
    @Named("SingleThread")
    public Executor provideSingleThreadExecutor() {
        return Executors.newSingleThreadExecutor();
    }

    @Provides
    @Singleton
    @Named("MultiThread")
    public Executor provideMultiThreadExecutor() {
        return Executors.newCachedThreadPool();
    }

}
public class MainActivity extends AppCompatActivity {

    @Inject
    @Named("SingleThread")
    Executor singleExecutor;

    @Inject
    @Named("MultiThread")
    Executor multiExecutor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyApplication.getInstance().getAppComponent().inject(this);
        setContentView(R.layout.activity_main);
    }
}

//  OR

@Component(modules = [...])
interface AppComponent {
 
   @Named("SingleThread")
   fun getSingleExecutor(): ServerApi
   
}

@Qualifier

Если необходимы разные объекты одного типа

Вместо аннотации @Named с текстовым значением можно создавать свои аннотации.
Для этого необходимо использовать Qualifier.

Создадим две аннотации: Prod и Dev:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Prod
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Dev

Используем их вместо аннотации Named:

@Prod
@Provides
fun provideServerApiProd(): ServerApi {
   return ServerApi("prod.server.com")
}
 
@Dev
@Provides
fun provideServerApiDev(): ServerApi {
   return ServerApi("dev.server.com")
}

Добавление аргумента к аннотации

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Prod(val value: String = "")

@Component(modules = [...])
interface AppComponent {
   @Prod("2")
   fun getServerApiProd(): ServerApi
}

@Singleton

Это Scope-аннотация, предоставляемая Даггером. Если перед методом, который провайдит зависимость, поместить @Singleton, то Даггер при инициализации Компонента, создаст единственный экземпляр помеченной зависимости, то есть синглтон. И при каждом затребовании данной зависимости будет предоставлять этот единственный экземпляр.

@Scope

Предоставляют возможность создания глобальных и "локальных синглтонов"

Приведем некоторые особенности Scope-аннотаций:

  1. Обычно scope-аннотации задаются для Компонента и provide-метода.
  2. Если хоть один provide-метод имеет scope-аннотацию, то Компонент должен иметь точно такую же scope-аннотацию.
  3. Компонент может быть "unscoped", только если во всех его модулях все provide-методы также "unscoped".
  4. Все scope-аннотации в рамках одного компонента (то есть для всех модулей с provide-методами, входящих в состав Компонента, и у самого Компонента) должны быть одинаковыми.

@Reusable

Область, которая указывает, что объект, возвращаемый привязкой, может (но не обязан) быть повторно использован.

Для каждого компонента, который использует @Reusable зависимость, данная зависимость кешируется отдельно

@Bind @Multibind @IntoSet @ElementsIntoSet @IntoMap

@assisted injection @Retention(RUNTIME)

7 steps to implement Dagger 2 in Android
Dagger 2. Часть первая. Основы, создание графа зависимостей, Scopes
Dagger 2. Часть вторая. Custom scopes, Component dependencies, Subcomponents
Dagger 2. Часть третья. Новые грани возможного

⚠️ **GitHub.com Fallback** ⚠️