Русский
Русский
English
Статистика
Реклама

Из песочницы MVP для Android преимущества использования Moxy в качестве вспомогательной библиотеки

В данной статье описываются преимущества использования Moxy в качестве вспомогательной библиотеки при использовании MVP для Android-приложения.


Важно: здесь не сравнивается MVP с другими архитектурными подходами типа MVVM, MVI и т.п. В статье описывается почему, если в качестве архитектуры UI-ной части приложения выбран MVP, то лучше не использовать самописную реализацию MVP, а использовать Moxy.


Библиотека Moxy позволяет избежать boilerplate кода для обработки lifecycle фрагментов и активитей, и работать с View как будто оно всегда активно.
Далее под View понимается имплементация View в виде фрагмента или активити.
Под интерактором понимается сущность бизнес-логики, т.е. класс, который лежит на более низком уровне абстракции, чем Presenter.


Общие преимущества Moxy


  • Активная поддержка и разработка библиотеки.
  • Поддержка фичей Kotlin вроде val presenter by moxyPresenter { component.myPresenter } и presenterScope для корутин.
  • Автоматическое восстановление состояния View.
  • Автоматическая увязка с жизненным циклом (а отсюда отсутствие утечек Активити и прочей подобной прелести).
  • Обращения к View происходят через не-nullable viewState. Нет риска, что какая-то команда View потеряется.
  • Весь lifecycle экрана сводится к двум коллбэкам презентера onFirstViewAttach() и onDestroy().
  • Время разработки экранов сокращается не нужно писать лишний код для обработки lifecycle и сохранения состояний.

Типичные задачи и решения


Рассмотрим, как решаются типичные задачи при разработке UI с использованием Moxy и без.


При решениях без Moxy предполагается следующая типичная реализация MVP. В presenter хранится nullable-ссылка на view. Presenter аттачится (передаётся ссылка на View) при создании View (в onCreate()) и детачится (зануляется ссылка на View) при уничтожении View (в onDestroy()).


Задача: асинхронный запрос данных и отображение результата на UI


Пусть у нас есть класс (MyListInteractor), который возвращает список данных. В presenter мы можем позвать его асинхронно для запроса данных.


class MyPresenter...// Функция запрашивает список и отображает его на UIoverride fun onDisplayListClicked() {        myListInteractor.requestList()            .subscribe { displayList(it) }

Решение с Moxy


private fun displayList(items: List<Item>) {    viewState.setListItems(items)}

Обращаемся к не-nullable viewState и передаём туда загруженные данные. Моху прикопает результат и отправит View, когда оно будет активно. При пересоздании View команда может быть повторена (зависит от стратегии) при этом заново данные не будут запрашиваться.


Решение без Moxy


private fun displayList(items: List<Item>) {    view?.setListItems(items)}

Обращаемся к View по nullable-ссылке. Эта ссылка зануляется при пересоздании View. Если к моменту завершения запроса view не приаттачена, то данные потеряются.
Возможное решение проблемы.


private fun displayList(items: List<Item>) {    view?.let { it.setListItems(items)         }?: let {             cachedListInteractor.saveList(items)          }}

Прикапывать данные в какой-то сущности, которая не связана с View (например, в интеракторе). При onResume() запрашивать прихранённые данные и отображать их.
Минусы решения.


  • Лишняя работа по сохранению результата из-за особенностей платформы (lifecycle Android активитей и фрагментов).
  • Прихранённые данные нужны только View, бизнес-логика будет перегружена проблемами сохранения результата.
  • Нужно следить за своевременной очисткой результата. Если объект, хранящий состояние, используется где-то ещё, то этот код также должен следить за его состоянием.
  • Presenter будет знать о View lifecycle, т.к. нужно уведомлять его об onResume(). Плохо, что Presenter знает об особенностях платформы.

Задача: сохранение состояния отображения


Часто возникает ситуация, когда нам нужно хранить какое-то состояние отображения.


Решение с Moxy


class MyPresenter...private var stateData: StateData = ...

Храним состояние в presenter. Presenter выживает при пересоздании View, поэтому там можно хранить состояние. Можно хранить данные любых типов, в т.ч. ссылки на интерфейсы, например.


Решения без Moxy


  • Можно хранить состояние в savedInstanceState или аргументах фрагмента.


    Минусы решения


    • View будет знать о state и, скорее всего, передавать его в presenter. Т.е. логика размазывается между View и presenter.
    • Возможно, понадобится явно из presenter обращаться к View с целью сохранить состояние, таким образом, в интерфейсе View будут лишние методы сохранения состояния (должны быть только методы для управления отображениям).
    • Presenter может не успеть обратиться к View для сохранения состояния, т.к. ссылка на View может занулиться и сам presenter может погибнуть.
    • Сохранить можно только примитивные и serializable/parcelable данные.
    • Boilerplate код для сохранения данных в Bundle и извлечения из Bundle.

  • Можно хранить данные на уровне бизнес-логики, в специальном классе (пусть этот класс будет отвечать только за state конкретной View и не будет использоваться где-то ещё).


    Минусы решения


    • Нужно следить за актуальностью данных.
    • Не просто разобраться, когда создавать и уничтожать класс, хранящий данные. Обычно его просто делают singleton'ом и он существует всегда, хотя нужен только одной View.


Задача: обмен данными между экранами


Часто бывает нужно результат действий на одном View отобразить на другом View.


Решение с Moxy


Обмен данными между экранами осуществляется так же как и асинхронный запрос. Разве что подписка на изменения идёт на subject или channel в интеракторе, в который presenter другой View кидает изменённые данные. Подписка в Presenter.onFirstViewAttach(), отписка в Presenter.onDestroy().


Решения без Moxy


  • Как и выше, через интерактор с subjectами или channelами. В этом случае подписываться/отписываться нужно в каждом onCreate()/onDestroy(). Так же есть риск потери данных, как в случае асинхронного запроса.
  • Через Broadcast или интент активити. Данные передаются через Bundle. Отсюда тот же Boilerplate с Bundle, как описано в разделе о сохранении состояния. Кроме того, в случае интента активити, логика по обмену данными ложится на View, хотя должна быть исключительно в presenter.

Задача: инициализация чего-либо, связанного с экраном


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


Решение с Moxy


Проинициализировать компонент можно в Presenter.onFirstViewAttach() и освободить в Presenter.onDestroy() это единственные коллбэки, о которых нам нужно задумываться.
Presenter.onFirstViewAttach() вызывается при самом первом создании View,
Presenter.onDestroy() вызывается при окончательном уничтожении View.


Решение без Moxy


Можно проинициализировать в onCreate() и освободить в onDestroy() активити или фрагмента.
Минусы решения


  • Постоянная переинициализация компонента.
  • Если компонент содержит коллбеки, то возможна утечка памяти (объекта presenter или активити/фрагмента).

Задача: показ AlertDialog


Особенностью использования AlertDialog является то, что он пропадает при пересоздании View. Поэтому при пересоздании View нужно заново отображать AlertDialog.


class MyFragment : ... {    private val myAlertDialog = AlertDialog.Builder(context)...    override fun switchAlertDialog(show: Boolean) {        if (show) myAlertDialog.show() else myAlertDialog.dismiss()    }

Решение с Moxy


@StateStrategyType(AddToEndSingleStrategy::class)fun switchAlertDialog(show: Boolean)

Выбрать правильную стратегию. Диалог сам перепокажется при пересоздании View.


Решения без Moxy


  • Можно в View хранить состояние отображения AlertDialog и перепоказывать при пересоздании View. Получается лишний boilerplate просто чтобы восстановить диалог.
  • Можно использовать DialogFragment. Для простых диалогов лишний overhead. И это добавляет проблемы с commit() фрагментов после onSaveInstanceState().

Особенности использования Moxy


Moxy позволяет избежать boilerplate кода при использовании MVP в android-приложении. Но, как и любой другой библиотекой, Moxy нужно научиться пользоваться. К счастью, использовать Moxy легко. Далее описаны моменты, на которые нужно обратить внимание.


  • Как команда View (т.е. вызов метода View) будет повторяться при пересоздании View зависит от стратегии над данным методом интерфейса View. Важно понимать, что означают стратегии. Неправильный выбор стратегии может негативно сказаться на UX. К счастью, стандартных стратегий не много (всего 5) и они хорошо документированы, а создание кастомных стратегий требуется не часто.
  • Moxy обращается к View с того потока, с которого обратился к нему presenter. Поэтому нужно самостоятельно следить в presenter за тем, чтобы методы viewState вызвались из главного потока.
  • Moxy не решает проблему commit() фрагментов после выполнения onSaveInstanceState(). Разработчики Moxy рекомендуют использовать commitAllowingStateLoss(). Однако, это не должно вызывать беспокойство, т.к. за состояние View полностью отвечает Moxy. То, что где-то внутри android потеряется состояние View нас не должно волновать.
  • Взаимно отменяющие команды view лучше объединять в один метод View. Например, скрытие и показ прогресса лучше сделать так:

@StateStrategyType(AddToEndSingleStrategy::class)fun switchProgress(show: Boolean)

а не так:


@StateStrategyType(AddToEndSingleStrategy::class)fun showProgress()@StateStrategyType(AddToEndSingleStrategy::class)fun hideProgress()

Либо можно использовать кастомную стратегию с тегами. Например, как описано тут: http://personeltest.ru/aways/habr.com/ru/company/redmadrobot/blog/341108/
Это нужно чтобы команда показа прогресса не вызвалась больше после команды скрытия прогресса.


  • Presenter должен по-особому инжектится через dagger.
    Например, может возникнуть желание сделать так:

class MyFragment : ... {    @Inject    lateinit var presenter: MyPresenter    @ProvidePresenter    fun providePresenter(): MyPresenter {        return presenter    }

Так делать нельзя. Нужно чтобы функция @ProvidePresenter гарантированно создавала новый инстанс presenter. Здесь при пересоздании фрагмента появится новый инстанс presenter. А Moxy будет работать со старым, т.к. функция providePresenter() вызывается только один раз.
Как вариант, можно в providePresenter() просто создать новый инстанс presenter:


@ProvidePresenterfun providePresenter(): MyPresenter {    return MyPresenterImpl(myInteractor, schedulersProvider)}

Это не очень удобно ведь придётся инжектить в фрагмент все зависимости этого presenter.
Можно из компонента dagger сделать метод для получения presenter и позвать его в providePresenter():


@Component(modules = ...)@Singletoninterface AppComponent {...    fun getMyPresenter(): MyPresenter}

class MyFragment : ... {...@ProvidePresenterfun providePresenter(): MyPresenter {    return TheApplication.getAppComponent().getMyPresenter()}

Важно, чтобы провайдер presenterа и сам presenter не были помечены аннотацией Singleton.


В последних версиях Moxy можно использовать делегаты kotlin:


private val myPresenter: MyPresenter by moxyPresenter {        MyComponent.get().myPresenter }

Ещё можно заинжектить через Provider:


@Injectlateinit var presenterProvider: Provider<MyPresenter>private val presenter by moxyPresenter { presenterProvider.get() }

Итог


Moxy замечательная библиотека, которая позволяет значительно упростить жизнь android-разработчика при использовании MVP.
Как и с любой новой технологией или библиотекой, в начале использования Moxy неизбежно возникают ошибки, например, не верный выбор стратегии или не правильный inject Presenter'а. Однако с опытом всё становится просто и понятно и уже сложно себе представить MVP без использования Moxy.
Выражаю благодарность сообществу Moxy за такой замечательный инструмент. А так же участникам telegram-чата Moxy за ревью и помощь в написании статьи.


Ссылки


Moxy реализация MVP под Android с щепоткой магии отличная статья от разработчиков Moxy с описанием того, для чего создавалась Moxy и как с ней работать.
Стратегии в Moxy (часть 1) статья хорошо описывает стандартные стратегии Moxy.
Стратегии в Moxy (Часть 2) руководство по созданию кастомных стратегий Moxy.
Об использовании популярных практик в разработке под Android высокоуровнево описывается MVP и Moxy.
Moxy. Как правильно пользоваться? / Юрий Шмаков (Arello Mobile) запись с конференции AppsConf, где разработчик Moxy рассказывает о том, как пользоваться библиотекой.

Источник: habr.com
К списку статей
Опубликовано: 15.06.2020 20:17:21
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Разработка под android

Android

Mvp мобильного приложения

Moxy

Категории

Последние комментарии

© 2006-2020, personeltest.ru