Dagger - makstron/info GitHub Wiki
Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android.
- Setup
- @Inject
- @Module
- @Provide
- @Component
- @Component.Builder
- @BindsInstance
- @SubComponent
- Lazy
- Provider
- @Named
- @Qualifier
- @Singleton
- @Scope
- @Reusable
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'
}
Существует несколько способов запроса зависимости
- Внедрение в конструктор класса
- Если один из конструкторов класса помечен этой аннотацией, то компонент использует этот конструктор, чтобы создать объект без участия модулей.
//тут можно поместить @Scope зависимости
public class ManagerA{
@Inject
public ManagerA(Context context){ /* */}
}
- Внедрение через метод. Метод будет выполнен после вызова конструктора
@Inject
public void register(SomeDepends depends){
depends.register(this);
}
- Внедрение в поля класса. Поля должны быть не приватными и не финальными
@Inject ManagerB managerB;
- Вызов геттера необходимого нам объекта. Этот геттер также используется для связывания нескольких графов зависимостей.
managerA = component.getManagerA();
Модуль – это фабрика объектов, разрешающая наши зависимости (это классы, способные создавать экземпляры определенных классов).
Модуль должен быть помечен аннотацией @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;}
}
Методы внутри @Module, “говорящие Dagger, как мы хотим сконструировать и предоставить зависимость
Компонент является связующим звеном между модулями и просителями зависимостей.
Отдать зависимость можно
- через метод компонента (в который будет передан объект, запрашивающий зависимости)
- через геттер (который вернёт зависимость).
В одном компоненте могут быть как методы, так и геттеры. Названия методов или геттеров не важны.
В обоих случаях мы сначала создаём интерфейс и помечаем его аннотацией @Component
или @Subcomponent
. Далее указываем, как будут разрешаться зависимости. Необходимо добавить список модулей, которые будут генерировать зависимости.
@Component(modules={AppModule.class})
interface AppComponent{
void injectInto(App holder);
ManagerA getManagerA ();
}
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
fun app(@HostUrl url: String): Builder
fun app(@AppId appId: String): Builder
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 - 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()
Ленивое создание объекта
Оборачиваем 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
}
При каждом вызове 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
}
Если необходимо провайдить несколько объектов одного типа
@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
}
Если необходимы разные объекты одного типа
Вместо аннотации @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
}
Это Scope-аннотация, предоставляемая Даггером. Если перед методом, который провайдит зависимость, поместить @Singleton, то Даггер при инициализации Компонента, создаст единственный экземпляр помеченной зависимости, то есть синглтон. И при каждом затребовании данной зависимости будет предоставлять этот единственный экземпляр.
Предоставляют возможность создания глобальных и "локальных синглтонов"
Приведем некоторые особенности Scope-аннотаций:
- Обычно scope-аннотации задаются для Компонента и provide-метода.
- Если хоть один provide-метод имеет scope-аннотацию, то Компонент должен иметь точно такую же scope-аннотацию.
- Компонент может быть "unscoped", только если во всех его модулях все provide-методы также "unscoped".
- Все scope-аннотации в рамках одного компонента (то есть для всех модулей с provide-методами, входящих в состав Компонента, и у самого Компонента) должны быть одинаковыми.
Область, которая указывает, что объект, возвращаемый привязкой, может (но не обязан) быть повторно использован.
Для каждого компонента, который использует @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. Часть третья. Новые грани возможного