Dagger 2 – это элементарно (Часть 2)

  • Tutorial
Предыдущая часть

Содержание

  1. Внедрение методов и полей
  2. Отложенная инициализация в dagger
  3. Модули dagger. Когда dagger вас не понимает
  4. Аннотация Named. Несколько экземпляра одного типа

Внедрение методов и полей

В первой части описан метод внедрение зависимости на уровне конструктора. Кроме этого, dagger может внедрять зависимости для полей и методов. Но эти внедрения следует использовать при крайней необходимости.

Пример внедрения метода:

class Car @Inject constructor(private var engine: Engine){
    var key: Key? = null
        @Inject set
}

class Key @Inject constructor()
 

На примере выше внедряется зависимость метода set для поля key

Внедрение полей происходит в три этапа:

  1. Добавить метод внедрения в абстрактную фабрику
  2. Определить поля которые будут внедрены
  3. Использовать методы внедрения в имплементации dagger абстрактного класса для внедрения зависимостей

Наглядно будет проще понять это, по порядку

1. В наш абстрактный класс добавим метод внедрения для MainActivity

@Component
interface DaggerComponent {
    fun getCar(): Car
    fun getEngine(): Engine
    fun getFuel(): Fuel

    fun inject(act: MainActivity)
}

2. Определим поля которые должны быть внедрены в MainActivity. Внедряемые поля должны быть lateinit var и видны всем (public)

@Injected 
lateinit var car: Car

3. Вызываем добавленным нами метод inject() абстрактного класса для внедрения полей активити.

В конечном итоге наш класс MainActivity будет выглядеть след. образом:

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var car: Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        DaggerDaggerComponent.create().inject(this)
    }
}

Отложенная инициализация в dagger

Как известно при запуске приложения не всегда нужны экземпляры всех классов. Это ускоряет первый запуск и не только. В dagger есть 2 вида инициализации внедряемых объектов: Provider<> И Lazy<>

Provider – инициализация происходит при первом обращении к объекту и при каждом вызове будет возвращен новый экземпляр объекта
Lazy – инициализация происходит при первом обращении, далее возвращается ранее кешированный экземпляры

Для применения данных видов инициализации необходимо «обернуть» инициализируемые объекты в нужный вид.

Пример использования Provider:

class Engine @Inject constructor(private var fuel: Fuel){
    fun start(){
        if(fuel!=null){
            print("Started!")
        }else{
            print("No more fuel!")
        }
    }
}

class Car @Inject constructor(private var engine: Provider<Engine>){
    var key: Key? = null
        @Inject set

    fun startCar(){
        engine.get().start()
    }
}

class Key @Inject constructor()

При каждом вызове метода get() получаем новый экземпляр нужного объекта.

Пример использования Lazy:

class Fuel @Inject constructor() {
    val fuelType = if(BuildConfig.DEBUG){
        "benzine"
    }else{
        "diesel"
    }
}

class Engine @Inject constructor(private var fuel: Lazy<Fuel>){
    fun start(){
        if(fuel!=null){
            print("Started with ${fuel.get().fuelType}")
        }else{
            print("No more fuel!")
        }
    }
}

При каждом вызове метода get() получаем один и тот же экземпляр.

Внимание! При добавлении метода get() у вида Lazy в Android Studio метод может быть подчеркнут красным т.к. у Kotlin есть свой класс Lazy. По этому импортируем класс dagger

import dagger.Lazy

Модули dagger. Когда dagger вас не понимает

Бывают такие случаи когда dagger не понимает ваши намерения. На пример у нашего класса Car есть поле типа (интерфейс) Driver, который наследуется классом Ivanov

При попытке внедрить поле с типом интерфейс вы получите ошибку «cannot be provided without an @Provides-annotated methods».

Для решения этой проблемы dagger предлагает использовать Модули. Модули обеспечивают dagger дополнительной информацией которые он не может получить самостоятельно. В качестве модули можно использовать интерфейсы или объекты (object).

Для решения задачи выше создадим модуль:

@Module
interface DaggerModul {
    @Binds
    fun bindDriver(driver: Ivanov): Driver
}

class Ivanov @Inject constructor(): Driver

В методе bindDriver мы объясняем dagger как необходимо инициализировать интерфейс.

Так же в компоненте нужно перечислить все существующие модули dagger

@Component(modules = [DaggerModul::class])
interface DaggerComponent {
…
}

Предположим для нашего класса Engine используется поле сторонней библиотеки cylinder (интерфейс). Как описать такое поле для dagger если не понятно какой класс будет инициализирован в runtime?

До сих пор мы использовали аннотацию для того, чтоб объяснить dagger как нужно внедрять зависимости. Как быть если вы не знаете, как нужно создавать классы, из чужих библиотек, на пример?

Аннотация Provides описывает те случаи когда нужно явно описать экземпляр какого класса нужно инициализировать.

@Module
object DaggerModuleObject {
    @Provides
    @JvmStatic
    fun getBoschCylinder(): Cylinder = BoschCylinder()
}

Тем самым мы говорим dagger что при инициализации поля cylinder нужен экземпляр класса BoschCylinder.

Аннотация Named. Несколько экземпляра одно типа

Бывают случаи, когда нужно создать экземпляры одного класса с разными настройками. В нашем примере это разные цвета на кузове и дверей.

При попытке построить проект со след. Модулем получим ошибку «(наш класс) Color is bound multiple times»

@Provides
@JvmStatic
fun getColorRed():Color =  Color("red")

@Provides
@JvmStatic
fun getColorBlue():Color =  Color("blue")

Для решения таких случаев используется аннотация Named. В первую очередь в модуле создадим 3 новых метода для инициализации в dagger

@JvmStatic
@Provides
fun getColor(): Color = Color("")

@Provides
@Named("blueColor")
@JvmStatic
fun getColorBlue(): Color{
    return Color("blue")
}

@JvmStatic
@Named("redColor")
@Provides
fun getColorRed(): Color = Color("red")

Первый метод по умолчанию, без него dagger будет ругаться об отсутствии класса «cannot be provided without an Inject constructor or an Provides-annotated method»

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

class Door @Inject constructor() {
    @Named("blueColor")
    @Inject
    lateinit var color:Color
}

class Car @Inject constructor(private var engine: Provider<Engine>, private var door: Door,  var driver: Driver){
    var key: Key? = null
        @Inject set

    @Inject
    @Named("redColor")
    lateinit var color: Color

    fun startCar(){
        engine.get().start()
    }
}

class Key @Inject constructor()

Исходник
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 0

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

Самое читаемое