February 24, 2020

Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень. Часть 2

Это шестая статья цикла «Dagger 2 для начинающих Android разработчиков». Если вы не читали предыдущие, то можете найти их здесь: первая статья, вторая статья, третья статья, четвертая статья, пятая статья.

Ранее в цикле статей

Мы рассмотрели пример проекта и попытались избавиться от сильных связей с помощью внедрения зависимостей, используя Dagger 2 и аннотации.

Также изучили три новые аннотации. @Scope для создания объектов в единственном экземпляре (singleton). @Named для разделения методов, предоставляющих объекты одинакового типа. @Qualifier как альтернативу @Named.

Создание нескольких Component

В предыдущей статье мы создали зависимости уровня приложения. Но что, если требуются зависимости только для уровня Activity? Activity создается и уничтожается в своем жизненном цикле, а что происходит с зависимостями? Зависимости, созданные внутри Activity, уничтожаются вместе с Activity.

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

Для объяснения этого я не хочу добавлять новые объекты в ранее рассмотренный проект. Вместо этого рассмотрим нашу MainActivity как отдельный объект и создадим для него свой модуль и компонент.

Взгляните на ветку Dagger2Part2.

Шаг 1. Создание области уровня Activity

Для предстоящих изменений я создал отдельный пакет с названием MainActivityFeature.

Создадим новую область (Scope) для MainActivity.

@Scope
annotation class MainActivityScope

Шаг 2. Создание компонента для MainActivity

Далее создадим отдельный компонент (Component) для MainActivity и пометим его только что созданной аннотацией.

@Component(dependencies = [RandomUserComponent::class])
@MainActivityScope
interface MainActivityComponent {
    fun getRandomUserAdapter(): RandomUserAdapter
    fun getRandomUserService(): RandomUsersApi
}

Необходимо позволить MainActivityComponent ссылаться на RandomUserComponent, для чего используется атрибут dependencies. Другими словами, этот атрибут говорит Dagger 2 обращаться к RandomUserComponent, если требуются дополнительные зависимости.

В данном примере на уровне Activity нам потребуются адаптер для API и объект для создания вызовов к RandomUsersAPI. Следовательно, реализуем методы getRandomUserAdapter() и getRandomUserService().

Шаг 3. Создание модуля для MainActivity

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

@Module
class MainActivityModule(
    private val mainActivity: MainActivity
) {

    @Provides
    @MainActivityScope
    fun randomUserAdapter(picasso: Picasso): RandomUserAdapter {
        return RandomUserAdapter(mainActivity, picasso)
    }
}

Заметка: Внедрение MainActivity через адаптер не обязательно, я сделал это для примера. Если нужен контекст для Picasso, то можно использовать holder.imageView.getContext().

Обратите внимание на аннотацию @MainActivityScope, которая добавлена к методу randomUserAdapter(), чтобы ограничить область использования зависимости уровнем Activity.

Также необходимо сопоставить этот модуль с соответствующим компонентом. Воспользуемся атрибутом modules.

@Component(
    modules = [MainActivityModule::class],
    dependencies = [RandomUserComponent::class]
)
@MainActivityScope
public interface MainActivityComponent {
    fun getRandomUserAdapter(): RandomUserAdapter
    fun getRandomUserService(): RandomUsersApi
}

Шаг 4. Создание класса Application

// добавьте имя этого класса в манифест
class RandomUserApplication : Application() {

    private lateinit var appComponent: RandomUserComponent

    override fun onCreate() {
        super.onCreate()

        randomUserApplicationComponent = DaggerRandomUserComponent.builder()
                .contextModule(ContextModule(this))
                .build()
    }

    fun getRandomUserApplicationComponent(): RandomUserComponent {
        return randomUserApplicationComponent
    }

    companion object {
        fun get(activity: Activity): RandomUserApplication {
            return activity.getApplication() as RandomUserApplication
        }
    }
}

Этот класс, наследуемый от Application, содержит все зависимости уровня приложения — RandomUserApplicationComponent.

Шаг 5. Доработка MainActivity

Если вы соберете проект, то Dagger 2 сгенерирует для вас класс DaggerMainActivityComponent. Для использования зависимостей уровня Activity нам понадобится получить некоторые зависимости уровня приложения.

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle) {
       ...
       val mainActivityComponent = DaggerMainActivityComponent.builder()
               .mainActivityModule(MainActivityModule(this))
               .randomUserComponent(RandomUserApplication.get(this).getRandomUserApplicationComponent())
               .build()
        randomUsersApi = mainActivityComponent.getRandomUserService()
        adapter = mainActivityComponent.getRandomUserAdapter()
        ...
    }
}

Заметка: взгляните на метод afterActivityLevelComponent() в ветке с проектом.

Шаг 6. Поздравьте себя

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

А что если в компоненте 50 зависимостей?

Если существует действительно много зависимостей, нужно ли нам постоянно писать выражения вроде того, что ниже?

randomUserApi = mainActivityComponent.getRandomUserService();
mAdapter = mainActivityComponent.getRandomUserAdapter();
…

Вы можете решить, что для вас это не важно, но и для этой проблемы есть решение.

Использование аннотации @Inject

Вместо того чтобы указывать Dagger 2, что вам необходимы RandomUserService и RandomUserAdapter, пусть Dagger 2 обрабатывает поле, которые мы пометим аннотацией @Inject.

Изменив классы так, как показано ниже, мы сможем начать использовать аннотацию @Inject в кратчайшие сроки. Полный пример вы можете просмотреть в следующей ветке.

Доработка MainActivityComponent

Удалим методы getRandomUserService() и getRandomUserAdapter() и добавим метод для внедрения MainActivity.

@Component(
    modules = [MainActivityModule::class],
    dependencies = [RandomUserComponent::class]
)
@MainActivityScope
public interface MainActivityComponent {

    fun injectMainActivity(mainActivity: MainActivity)
}

Доработка MainActivity

class MainActivity : AppCompatActivity() {
    ...
    @Inject
    lateinit var randomUsersApi: RandomUsersApi

    @Inject
    lateinit var adapter: RandomUserAdapter
    ...
    
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ...
        val mainActivityComponent = DaggerMainActivityComponent.builder()
                .mainActivityModule(MainActivityModule(this))
                .randomUserComponent(RandomUserApplication.get(this).getRandomUserApplicationComponent())
                .build()
        mainActivityComponent.injectMainActivity(this)
        ...
    }
}

Как это работает? Когда Dagger 2 находит метод без возвращаемого значения, он понимает, что должно быть что-то, что ему нужно в классе, то есть он будет инициализировать в классе поля, помеченные аннотацией @Inject.

То что нужно! Теперь код можно запускать.

Резюме

Мы рассмотрели пример внедрения зависимостей на уровне Activity. Также увидели пример использования аннотации @Inject.

В заключение

Спасибо, что потратили свое время на чтение и поддержку этой серии статей. Я надеюсь, вы получили некоторое представление о зависимостях и Dagger 2. Причина, по которой я написал эту серию статей, заключается в том, что я улучшал свои знания Dagger 2 чтением большого количества статей в разных блогах, но получил ещё больше знаний, пока писал эти статьи для вас. Поэтому призываю всех читателей делиться своими знаниями любыми возможными путями. Я не эксперт в Dagger 2, я считаю себя лишь учеником.

Теперь вы можете продолжить изучать Dagger 2 дальше, эта серия статей должна была сформировать у вас достаточно понимание того, как он работает.

Ссылки на другие ресурсы (на английском)

Источник: Dagger 2 для начинающих Android разработчиков. Dagger 2. Продвинутый уровень. Часть 2