Разработка под Android
27 декабря 2018

MVP и Dagger 2 – скелет Android-приложения – часть 2

Tutorial
Данная статья является результатом изысканий, побочным продуктом которых стало воплощение давней идеи в одном очень полезном и очень не хватавшем мне когда-то Android-приложении – My Location Notifier, предназначенном для автоматического оповещения адресата о прибытии пользователя (см. подробное описание по ссылке).

Итак, в первой части мы построили основной скелет Android-приложения в соответствии с архитектурой MVP. Теперь начнем прикручивать к нему Dagger 2.

2.1. Предварительная настройка

Для начала, перейдем в файл build.gradle (Project: mymvcapp) и добавим в раздел buildscript следующую строчку:

ext.dagger2_version = "2.8"

На момент написания этой статьи это последняя версия Dagger 2. Далее, перейдем в файл build.gradle (Module: app) и добавим в начало строчку:

apply plugin: 'kotlin-kapt'

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

Теперь добавим необходимые зависимости:

implementation "com.google.dagger:dagger:$dagger2_version"
kapt "com.google.dagger:dagger-compiler:$dagger2_version"

2.2. Подготовка к инъекции

Синхронизируем проект и вернемся к компоненту MainScreen. Создадим в пакете backstage класс di.MainScreenModule и пометим его аннотацией Module. В новом классе объявим и имплементируем метод providesPresenter(): MainScreenPresenter = MainScreenPresenter и пометим его аннотациями Provides и Singleton. Так теперь будет выглядеть наш класс:

@Module
class MainScreenModule {
    @Provides
    @Singleton
    fun providesPresenter(): MainScreenPresenter = MainScreenPresenter()
}

Теперь перейдем в класс MainScreenCompatActivity и заменим модификаторы переменной presenter на lateinit var, удалим присвоение значения и пометим ее аннотацией Inject:

class MainScreen : BaseCompatActivity(), MainScreenContract.View {
    @Inject
    lateinit var presenter: MainScreenPresenter

    override fun init(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main_screen)
        presenter.attach(this)
    }
}

Заметьте, вопросительный знак нам теперь при обращении к этой переменной не нужен.
Может показаться, что все, инъекция произведена, задача выполнена. А вот и нет. Теперь нам необходимо заставить Dagger сгенерировать необходимый код для внедрения нашей первой зависимости. Именно для этого мы выше добавляли в скрипт сборки плагин kotlin-kapt. В пакете com.caesar84mx.mymvcapp создаем пакет di.config, в котором создадим интерфейс AppDiComponent.



Теперь, объявим интерфейс компонентом и синглтоном и мизераблем, зарегистрируем в нем наш модуль, а внутри интерфейса объявим метод inject(mainScreenActivity: MainScreen):

@Component(
    modules = [
        MainScreenModule::class
    ]
)
@Singleton
interface AppDiComponent {
    fun inject(mainScreenActivity: MainScreen)
}

А теперь, необходимо, наконец, заставить Dagger сгенерировать весь необходимый код. Для этого, создадим в пакете config.di класс MyMvpApp, унаследуем его от класса Application, зарегистрируем класс в AndroidManifest.xml, прописав в теге application строчку android:name=".config.di.MyMvpApp". Далее, объявляем переменную lateinit var injector: AppDiComponent, устанавливаем ей приватный сеттер, переопределяем метод onCreate(). И начинаем магию:

class MyMvpApp: Application() {
    lateinit var injector: AppDiComponent
        private set
    override fun onCreate() {
        super.onCreate()
        injector = DaggerAppDiComponent.builder()
            .mainScreenModule(MainScreenModule())
            .build()
    }
}

Как вы успели заметить, класса DaggerAppDiComponent еще не существует, он будет сгенерирован при билде приложения. Равно как и имплементация нашего компонента. Имя класса составляется из слова “Dagger” + названия интерфейса, помеченного как компонент. Метод mainScreenModule() тоже будет сгенерирован при билде проекта, имя должно составляться из имени класса инъектируемого модуля в lowerCamelCase.

Собираем проект (Build → Make Project). Наслаждаемся автоматической кодогенерацией и продолжаем.

2.3. Инъекция

Внимание: далее, будут представлены некоторые пляски с бубном с элементами порно. Просьба убрать от экрана детей и нервных личностей.

Нам, для успешных инъекций будет необходима ссылка на переменную injector. Согласитесь, создавать экземпляр MyMvpApp в каждом классе, где мы производим инъекцию – не самое удачное решение. Поэтому, мы сделаем следующее:

class MyMvpApp: Application() {
    lateinit var injector: AppDiComponent
        private set

    override fun onCreate() {
        super.onCreate()

        INSTANCE = this
        injector = DaggerAppDiComponent.builder()
            .mainScreenModule(MainScreenModule())
            .build()
    }

    companion object {
        private var INSTANCE: MyMvpApp? = null
        @JvmStatic
        fun get(): MyMvpApp = INSTANCE!!
    }
}

Выдохнули, вернулись в класс MainScreen. Тепепь, в метод init() инжектим наш презентер. Не забудьте, что это действие необходимо выполнять до первого обращения к инъектируемой переменной. Так теперь выглядит наш класс:

class MainScreen : BaseCompatActivity(), MainScreenContract.View {
    @Inject
    lateinit var presenter: MainScreenPresenter

    override fun init(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main_screen)
        MyMvpApp.get().injector.inject(this)
        presenter.attach(this)
    }
}

А вот как выглядит вся базовая структура нашего приложения:



2.4. Заключение

Итак, у нас готова минимальная структура приложения, на которую далее только остается навешивать элементы. Нужна новая активность? Представляем ее компонентом, отделяем ui от backstage, для каждого компонента определяем, какие зависимости нам нужны (минимум, презентер в активности, а может, в самом презентере API для взаимодействия с удаленным сервисом, или, например, API репозитория для работы с БД), прописываем модуль с зависимостями, регистрируем модуль в компоненте, прописываем в билдере, пересобираем проект, инжектим зависимости где необходимо, повторяем итерацию для каждого нового компонента.

Разумеется, может возникнуть вопрос: а зачем нам нужен Dagger? Без него ведь отлично справлялись? Отлично, пока приложение маленькое. Когда оно разрастется до полноценного, с десятками активностей, фрагментов, адаптеров, с запросами на сервер, кэшированием данных и прочими чудесами, возникнет множество зависимостей, отследить которые в большом приложении довольно трудно, если не использовать Dependency Injection. DI-фреймворк Dagger помогает упростить задачу их внедрения и отслеживания.

+1
3,2k 34
Комментарии 4