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

  • Tutorial
Данная статья нацелена на новичков в Android-разработке и призвана помочь в создании минимально необходимой структуры приложения.

Так получилось, что я относительно недавно начал программировать под Android – после месяца без проекта в компании, где я работаю, меня определили в команду мобильной разработки в уругвайском офисе Tata Consultancy Services. При беседе с тимлидом команды мне был озвучен стек, с которым мне предстояло сначала ознакомиться, а затем и овладеть. В числе прочего был фреймворк Dagger 2 для DI и MVP в качестве архитектурного паттерна. И Kotlin. Но о нем в другой раз :)

Таким образом, я приступил к изучению сначала основы Android SDK, а затем и всего сопутствующего стека. С самим SDK проблем не возникло – исчерпывающей информации по нему в сети более чем достаточно, начиная с официальной документации и заканчивая туториалами (особенно с этим помог проект startandroid), но с Dagger 2 и MVP применительно к Android-разработке возникли некоторые затруднения ввиду довольно куцей документации первого и, на тот момент, недостаточного понимания второго. Дело в том, что до мобильной разработки я делал микросервисы на Java с использованием Spring Boot/MVC и уже имел достаточное представление и о том, что такое Dependency Injection, и о том, что такое MVC. При том, даже само название “Spring MVC” предполагает, что этот паттерн заложен в архитектуру проекта и его использование очевидно. От Dagger 2 я ожидал как такой же как в Spring “магии” и настолько же проработанной документации и туториалов. И обломался :P

Тем не менее, при достаточном упорстве и усидчивости, нет ничего невозможного, а результатом изысканий стало осуществление моей давней идеи, возникшей еще в те времена, когда об Android-разработке я и не помышлял. Оценить идею вы можете, установив приложение из Google Store

В данной статье я хотел бы представить сухую выжимку результата моих поисков – пошаговое руководство по созданию скелета Android-приложения с использованием MVP и Dagger 2. Итак, начнем.

1.1 Abstracts


Для начала, создадим пакет abstracts в корне проекта, пусть это будет com.caesar84mx.mymvcapp.abstracts. В нем создадим 2 интерфейса: view.BaseView и presenter.BaseMvpPresenter. Следующим образом:



Это базовые архитектурные элементы, которые в дальнейшем будут использоваться в приложении. Далее, открываем BaseView и объявляем в него методы showView() getContext():

interface BaseView {
    fun showView(view: View, isShown: Boolean) {
        view.visibility = if (isShown) View.VISIBLE else View.GONE
    }

    fun getContext(): Context
}

Теперь открываем BaseMvpPresenter и редактируем его следующим образом:

interface BaseMvpPresenter<V: BaseView> {
    var isAttached: Boolean
    fun attach(view: V)
    fun detach()
}

В пакете view создаем абстрактный класс BaseCompatActivity, наследуем его от AppCompatActivity и имплементируем недавно созданный интерфейс BaseView. Внутри класса объявляем абстрактный метод init(savedInstanceState: Bundle?) и имплементируем метод getContext() из BaseView:

abstract class BaseCompatActivity: AppCompatActivity(), BaseView {
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        init(savedInstanceState)
    }

    protected abstract fun init(savedInstanceState: Bundle?)

    override fun getContext(): Context = this
}

От этого класса в дальнейшем мы будем наследовать все активности.

Теперь перейдем к презентеру – создадим класс BasePresenter, имплементирующий интерфейс BaseMvpPresenter и реализуем методы интерфейса следующим образом:

open class BasePresenter<V : BaseView> : BaseMvpPresenter<V> {
    protected var view: V? = null
        private set

    override var isAttached = view != null

    override fun attach(view: V) {
        this.view = view
    }

    override fun detach() {
        this.view = null
    }
}

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

1.2. Компоненты


Для начала, создадим пакет com.caesar84mx.mymvcapp.components, в нем пакет mainscreen, в котором, в свою очередь, пакеты ui и backstage, и перенесем в пакет ui класс MainScreen:



Теперь удалим из класса MainScreen автоматически сгенерированную при создании проекта имплементацию метода onCreate(), а также, наследование от AppCompatActivity и унаследуем его от BaseCompatActivity. Теперь реализуем метод init(), ранее объявленный в базовом классе. Весь код, который мы раньше поместили бы в метод onCreate(), мы поместим в него (как мы помним, метод init() вызывается в методе onCreate() базового класса):

class MainScreen : BaseCompatActivity() {
    override fun init(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main_screen)
    }
}

Великолепно, элемент view паттерна MVP создан, теперь перейдем в закулисье нашего компонента – пакет backstage. Создадим интерфейс MainScreenContract – так называемый контракт, через который мы и будем реализовывать наш паттерн. В данном интерфейсе создадим 2 подинтерфейса — Presenter и View:

interface MainScreenContract {
    interface Presenter: BaseMvpPresenter<MainScreenContract.View> 
    interface View: BaseView
}

Теперь, перейдем к презетнеру и создадим класс MainScreenPresenter:

class MainScreenPresenter :
    BasePresenter<MainScreenContract.View>(),
    MainScreenContract.Presenter {
}

Скелет приложения почти готов, осталось несколько штрихов. В класс MainScreen добавим имплементацию интерфейса MainScreenContract.View, создадим и проинициализируем переменную presenter: MainScreenPresenter, а в методе init() присоединим вид к презентеру следующим образом:

class MainScreen : BaseCompatActivity(), MainScreenContract.View {
    val presenter: MainScreenPresenter? = MainScreenPresenter()
    override fun init(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main_screen)
        presenter?.attach(this)
    }
}

Таким образом, мы создали презентер и добавили в него наш экземпляр view (не путать с android.view.View), который в презентере будет использоваться для манипуляций с видом.

1.3. Заключение первой части


Итак, мы создали базовые абстрактные элементы паттерна MVP, которые, однако, используются не напрямую, в лоб, а через т.н. контракт – базовый элемент каждого компонента приложения, который сочетает в себе как действия элемента view, так и действия элемента presenter. Контракт – достаточно гибкий элемент, состав которого варьирует от компонента к компоненту, ненавязчиво увязывая компоненты в рамках единой архитектуры.

Следует помнить, что в соответствии с концепцией MVP, элемент view должен быть максимально тупым, в нем мы производим только элементарные действия, такие, как, например, показать/спрятать текст, поменять фон или цвет текста, показать/спрятать значок загрузки и т.д. Методы, соответствующие этому элементу, мы определяем в подинтерфейсе View контракта. В то время, как логикой мы занимаемся в презентере – бизнес-логика, манипуляции данными (CRUD), запуск фоновых задач и т.д. В нем же мы решаем, когда и показать те или иные элементы на экране. В этом отличие от реализованной в спринге концепции MVC, где между бизнес-логикой и видом находится тупой контроллер, который только получает запросы от вида и вызывает сервис, который возвращает данные или выполняет иные действия, определенные бизнес-логикой. Методы, соответствующие презентеру, мы определяем в подинтерфейсе Presenter контракта.

При реализации презентера, манипуляции видом будут производиться через переменную view суперкласса BasePresenter, в то время, как методы, соответствующие view реализуются в классе активности.

Вы спросите, а где здесь Dagger 2 и зачем он нам сдался, не будет ли реализация DI в Android натягиванием совы на глобус? Ответ на второй вопрос – нет, не будет. А почему и зачем он нужен – во второй части моей статьи ;)
Поделиться публикацией

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

    +1
    view!!.visibility = if (isShown!!) View.VISIBLE else View.GONE
    Ну вот опять эти !! (двойной восклицательный знак) прямо кричит что тут явно что то не так. Ну почему нельзя немного подумать и написать безопасный код? понятно что это пример, но тут все просто — сделайте функцию fun showView(view: View?, isShown: Boolean?) без опциональных типов например и не нужны будут эти костыли в виде !!
      0
      Исправил, благодарю за замечание.
      0
      В своё время, когда переходил с Java на Kotlin, тоже постоянно использовал Nullable типы. Создаёте неизменяемую переменную, объявляете её Nullable типом и тут же задаёте ей значение… Восклицательные знаки в методе showView вообще треш

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

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