Android moxy что это
Стратегии в Moxy (часть 1)
Разработанный нами с Юрой фреймворк Moxy получил широкое распространение в разработке под Android. Он обеспечивает реализацию MVP паттерна при работе с Activity, Fragment и View, полностью отделяя “callback hell” их жизненного цикла от презентера.
Данное поведение реализовано за счет сущности ViewState, которая проксирует вызовы методов между Presenter и View, при этом сохраняя некоторые из них в очереди на основании специальных стратегий. При пересоздании View вызываются не все методы, а только те, которые находятся в очереди на данный момент.
В данной статье мы расскажем, как работают стратегии, предоставляемые “из коробки”, и в каких случаях стоит применять каждую из них. О механизм работы стратегий изнутри и написании кастомных стратегий читайте в Части 2.
Принципы работы Moxy
Очередь команд (методы, которые вызываются у view) в презентере определяют состояния View после пересоздания.
Сохранение команд в очереди происходит согласно стратегии, указанной для нее в методе интерфейса View. Сам ViewState генерируется во время компиляции на основании интерфейса View и используемых в нем аннотаций.
Неаккуратное использование стратегий может повлечь неправильное поведение приложения и переполнения памяти
Команды могут применяться ко View только когда оно находится в активном состоянии – в промежутке времени между вызовами методов getMvpDelegate().onAttach() и getMvpDelegate().onDetach() из Activity, Fragment или кастомной android.view.View.
Для иллюстрации работы стратегий мы будем использовать следующую схему:
Ось X представляет собой временную шкалу (направление времени слева-направо).
Кружками обозначены команды, одинаковые команды (соответственно имеющие одинаковую стратегию) обозначены кружками с одинаковыми цветом и цифрой.
На оси презентера отображены команды, которые инициирует презентер методом getViewState().doSomething(args). Команда, выполняемая презентером, имеет ту стратегию, которая рассматривается в данной схеме, если не указано обратного.
Ось ViewState представляет собой состояние очереди команд, которые применяются ко View при его последующем пересоздании. Команды применяются последовательно, от нижних к верхним. Исходное состояние ViewState отображается в крайнем левом положении на оси.
Ось View представляет собой набор команд, которые применились ко View. Значок смены ориентации означает пересоздание View. Ось View может начинаться с середины диаграммы – это означает что View перешло в активное состояние именно с этого момента.
Стратегии из коробки
Moxy по умолчанию предоставляет следующие стратегии:
AddToEndStrategy — выполнить команду и добавить команду в конец очереди
AddToEndSingleStrategy — выполнить команду, добавить ее в конец очереди и удалить все ее предыдущие экземпляры
SingleStateStrategy — выполнить команду, очистить очередь и добавить в нее команду
SkipStrategy — выполнить команду
OneExecuteStrategy — выполнить команду при первой возможности
Рассмотрим подробнее как они работают и когда их стоит применять.
AddToEndStrategy — самая простая стратегия, представляющая собой добавление команды в конец очереди:
При вызове презентером команды (2) со стратегией AddToEndStrategy:
Данная стратегия отличается от AddToEndStrategy тем, что в очереди ViewState может находиться только один экземпляр каждой команды, помеченной данной стратегией.
При вызове презентером команды (2) со стратегией AddToEndSingleStrategy:
Данная стратегия по своему принципу работы похожа на AddToEndSingleStrategy. Отличие заключается в том, что при попадании во ViewState команды с данной стратегией, очередь команд очищается полностью.
При вызове презентером команды (1) со стратегией SingleStateStrategy:
Skip стратегия не меняет стека ViewState. Команда применяется ко View, только в случае, если оно находится в активном состоянии.
Команда ведет себя по-разному, в зависимости от наличия вью в активном состоянии.
а) В случае наличия View в активном состоянии:
При вызове презентером команды (4) со стратегией SkipStrategy, в случае наличия View в активном состоянии:
При вызове презентером команды (4) со стратегией SkipStrategy, в случае отсутствия View в активном состоянии:
Данная стратегия очень похожа на SkipStrategy. Отличие заключается в том, что при отсутствии view в активном состоянии, команда дожидается ее появления, а затем применяется ровно один раз.
Команда ведет себя по-разному, в зависимости от наличия вью в активном состоянии.
а) В случае наличия View в активном состоянии:
При вызове презентером команды (4) со стратегией OneExecuteStrategy, в случае наличия View в активном состоянии (Поведение полностью аналогично SkipStretegy):
При вызове презентером команды (4) со стратегией OneExecuteStrategy, в случае отсутствия View в активном состоянии:
Отсутствие явного указания стратегии у метода ведет к ее автоматическому выводу на основании стратегии для всего интерфейса и стратеги по умолчанию. В настоящее время стратегией по умолчанию является AddToEndStrategy.
Область применения команд
В данном разделе мы собрали типичные случаи применения команд.
AddToEndStrategy — применяется в случае, когда нужно последовательно применить несколько команд, которые должен быть повторно применены при пересоздании View.
Пример: Экран организации состоит из 3-х блоков: общая информации, акции, история покупок. Данные части могут быть загружены и отображены на экране асинхронно. После пересоздания важно, отобразить информацию во всех блоках. Все методы будут помечены стратегией AddToEndStrategy
AddToEndSingleStrategy — применяется в случае, когда команда должна применяться при пересоздании view не более одного раза.
Пример: На экране есть индикатор загрузки, видимость которого меняется вызовом метода View: toggleLoading(visible: Boolean).
Cпойлер: здесь и далее примеры кода приведены на Kotlin.
Данный метод будет иметь стратегию AddToEndSingleStrategy.
SingleStrategy — применяется в случае, если нам не важен результат команд отработавших до нее.
Пример: Данные экрана профиля подгружаются из сети. Экран имеет 3 взаимоисключающие состояния: загрузка, отображение данных и экран—заглушка. Соответственно View имеет методы showLoading(), showData() и showStub(). Все три команды будут иметь стратегию SingleStateStrategy.
SkipStrategy — используется в случае, если нам необходимо выполнить некоторое действие, прямо сейчас и только в случае, если имеется View в активном состоянии.
Пример 1: Имеется экран редактирования данных пользователя. При нажатии кнопки сохранить, отображается индикатор загрузки посредством вызова команды toggleLoading(show = true). При неуспешной загрузке экран возвращается в исходное состояние командой toggleLoading(show = false) и отображается SnackBar с информацией об ошибке командой showLoadingError(). Последняя команда будет иметь стратегию SkipStrategy, т.к. результат ее выполнения нужен только при активном экране и не должен сохраняться при смене конфигурации.
Пример 2: Старт анимации после какого-то действия пользователя. Команду имеет смысл применять только один раз, причем при отсутствии активной вью не требуется сохранение ее в очередь.
OneExecuteStrategy — используется в случае, если нам необходимо выполнить некоторое действие при первом появление View в активном состоянии.
Пример 1: Открытие следующего экрана. Запуск нового Activity, Fragment или FragmentDialog удобно выполнять посредством данной стратегии. В этом случае мы будем иметь гарантированно 1 запуск.
OneExecuteStrategy и SkipStrategy очень похожи, будьте внимательны при их использовании.
Как избежать проблем из-за стратегий?
При использовании стратегий вы можете столкнуться с двумя видами проблем:
Проблема 1. Лишние команды в очереди
При использовании только AddToEndStrategy, которая является стратегией по умолчанию, очередь постепенно увеличивается. Это может вызывать:
Старайтесь минимизировать величину очереди команд, посредством выставления стратегий.
Проблема 2. Неправильное состояние при пересоздании view
Зачастую приложение требует только портретную ориентации, вследствии чего при разработке она фиксируется, однако пересоздание вью происходит не только при смене ориентации, но и при других сменах конфигурации.
При разработке оставлять возможность смены ориентации приложения. Это поможет вам контролировать не только правильность применения стратегий, но и остальные side-эффекты смену конфигурации устройства.
Стратегии в Moxy являются довольно гибким инструментом, неаккуратное использование которого может повлечь логические ошибки. Надеемся что данная статья сделает использование Moxy более прозрачным и очевидным.
В части 2 мы рассмотрим, механизм работы стратегий изнутри и как создавать кастомные стратегии.
Пусть ваш код всегда выполняется так, как вы его проектировали!
от авторов библиотеки Moxy Xanderblinov и senneco
MVP на стероидах: заставляем робота писать код за вас
Структура проекта
Для того чтобы начать кодогенерацию, нужно зафиксировать структуру проекта.
Наша цель — научиться генерировать эти классы
Настройка шаблонов
Как создавать шаблоны в Android Studio можно прочитать в статье от Fi5t про Тотальную шаблонизацию
Добавляем в проект шаблоны для Moxy:
Использование шаблонов
Далее меняем в поле Package Name слово blank на имя подпакета и нажимаем Finish. Ваш пакет классов готов! Стоит отметить, что при обновлении Android Studio может снести все кастомные шаблоны. В этом случае придется их импортировать заново.
Что в итоге
Концепция MVP подразумевает разделение логики приложения на слои и, как следствие, увеличение кодовой базы. Использование шаблонов и кодогенерации максимально избавляет вас от boilerplate, предохраняет от случайных ошибок и позволяет сосредоточиться на бизнес-логике. Пусть код за вас пишет робот!
Moxy — реализация MVP под Android с щепоткой магии
Что такое MVP
MVP – это способ разделения ответственности в коде приложения. Model предоставляет данные для Presenter. View выполняет две функции: реагирует на команды от пользователя(или от элементов UI), передавая эти события в Presenter и изменяет gui по требованию Presenter. Presenter выступает как связующее звено между View и Model. Presenter получает события из View, обрабатывает их(используя или не используя Model), и командует View о том, как она должна себя изменить.
MVP в Android
MVP снимает часть ответственности с Activity. Вся работа с асинхронными задачами уходит в Presenter. Вся бизнес-логика – в Presenter и Model. Activity, в свою очередь, становится View. Она начинает просто отображать то, что скажет Presenter и передаёт события в Presenter, чтобы тот решал, как быть дальше.
Moxy – теория
Наше решение сильно отличается от всех прочих(даже сама концепция MVP была модернизирована) тем, что между View и Presenter затесался ViewState. Причём он там абсолютно необходим. Он отвечает за то, чтобы каждая View всегда выглядела именно так, как того хочет Presenter. ViewState хранит в себе список команд, которые были переданы из Presenter во View. И когда „новая“ View присоединяется к Presenter, ViewState автоматически применяет к ней все команды, которые Presenter выдавал раньше. Таким образом получается, что не зависимо от того, что произойдёт со View по вине Android, View останется всё-равно в правильном состоянии. Для этого вам нужно будет только привыкнуть изменять View исключительно командами из Presenter. Заметим, что это одно из основных правил MVP и распространяется не только на Moxy.
Moxy – возможности
Moxy – MvpPresenter
Каждое приложение содержит в себе какую-то бизнес-логику. В концепции MVP, вся бизнес-логика располагается в Presenter и в Model. По факту это значит, что вы практически не программируете во View. Для того, чтоб ваш Presenter не превратился в God Object, нужно разделять каждый отдельный блок бизнес-логики в отдельный Presenter. В таком случае у вас получится много Presenter, но они будут очень простыми и понятными. Например, если у вас на одном экране было две бизнес-логики, а затем они разошлись на 2 разных экрана, то вы просто измените View. А Presenter какими были, такими и останутся. Так же, в этом случае вы сможете легко переиспользовать один Presenter в нескольких местах(например, BasketPresenter, сквозной через всё приложение). Ещё это упростит тестирование кода – вы просто проверите небольшой Presenter, что он всё делает правильно.
Moxy – MvpView и MvpViewState
Moxy – @GenerateViewState и @InjectViewState
Загвоздка заключается в том, что при генерации кода, мы должны знать типы всех параметров всех методов. Иначе полученный код будет неработоспособным.
Поэтому такой код писать можно:
А такой код писать нельзя:
Moxy – StateStrategy для команд во ViewState
Перед написанием своих стратегий, посмотрите на код уже реализованных – возможно он будет вам полезен.
Moxy – MvpDelegate и жизненный цикл MvpPresenter
Первый вариант. В аннотации @InjectPresenter вы выставляете значение для параметра tag. Тогда MvpDelegate попытается найти в глобальном хранилище Presenter с таким тэгом. Если он его найдёт, то просто установит его в это поле. Иначе он создаст подходящий Presenter, сложит его в хранилище, и установит его в это поле. С учётом того, что к одному Presenter может быть привязано несколько View, этот механизм открывает очень много возможностей перед вами.
Moxy – Model
Важным элементом MVP является Model. Но в Moxy эта часть MVP никак не затронута. Всё дело в том, что в этом нет смысла. В каждом проекте свои требования к Model. Где-то Model это просто набор классов для работы с API и сама работа с API(например, через Retrofit). Где-то в Model входит ещё и дополнительная бизнес-логика. В каких-то проектах актуально использование подхода Clean Architecture. В таком случае внутри Model появляются дополнительные сущности, например, Interactor и Repository. А с учётом того, что Presenter полностью отвязан от жизненного цикла Activity, вы можете спокойно создавать экземпляр конкретной Model внутри Presenter и работать с ним. Используя DI вы можете подключать нужную Model в Presenter. А в будущем, используя тот же DI, спокойно подменять Model для тестов.
Moxy – итого
В результате мы имеем библиотеку, которая решает все проблемы жизненного цикла. Вы всегда будете показывать пользователю именно то состояние, которое для него актуально, и в то же время, вам не придётся делать ничего лишнего. Только опишите все команды для View отдельными методами. И избегайте изменения View из самого View. Если вы показали диалог командой из Presenter, то и при закрытии диалога, должна быть команда из Presenter. Иначе ViewState снова покажет вам диалог после смены конфигурации.
Полезные материалы
Moxy – где брать
Чтобы подключить Moxy в свой проект, просто добавьте её в зависимостях. Moxy состоит из трёх частей. Одна из них отвечает за предоставление вам Moxy SDK. Её довольно просто подключить:
Другая часть отвечает за обработку аннотаций и занимается генерацией кода. И здесь вам нужно определиться.
Если у вас нет никаких особых требований, ваш проект – обычный Android-проект, и вы не хотите, чтобы сгенерированный код был доступен из вашего когда, то подключите зависимость так:
В момент, когда мы соберём репрезентативный список вопросов по нашей библиотеке, по тому, как её использовать, по MVP в целом, будет сделана отдельная статья, в которой будут освещены самые популярные/интересные вопросы. Вопросы можно задавать здесь в комментариях, писать мне(@senneco ) и ещё одному автору библиотеки – Xanderblinov. Или можете обращаться ко всему отделу Android-разработки Arello Mobile, написав на java-developers@arello-mobile.com.
От авторов библиотеки Moxy
senneco и Xanderblinov
MVP для Android — преимущества использования Moxy в качестве вспомогательной библиотеки
В данной статье описываются преимущества использования Moxy в качестве вспомогательной библиотеки при использовании MVP для Android-приложения.
Важно: здесь не сравнивается MVP с другими архитектурными подходами типа MVVM, MVI и т.п. В статье описывается почему, если в качестве архитектуры UI-ной части приложения выбран MVP, то лучше не использовать самописную реализацию MVP, а использовать Moxy.
Библиотека Moxy позволяет избежать boilerplate кода для обработки lifecycle фрагментов и активитей, и работать с View как будто оно всегда активно.
Далее под View понимается имплементация View в виде фрагмента или активити.
Под интерактором понимается сущность бизнес-логики, т.е. класс, который лежит на более низком уровне абстракции, чем Presenter.
Общие преимущества Moxy
Типичные задачи и решения
Рассмотрим, как решаются типичные задачи при разработке UI с использованием Moxy и без.
При решениях без Moxy предполагается следующая типичная реализация MVP. В presenter хранится nullable-ссылка на view. Presenter аттачится (передаётся ссылка на View) при создании View (в onCreate()) и детачится (зануляется ссылка на View) при уничтожении View (в onDestroy()).
Задача: асинхронный запрос данных и отображение результата на UI
Пусть у нас есть класс (MyListInteractor), который возвращает список данных. В presenter мы можем позвать его асинхронно для запроса данных.
Решение с Moxy
Обращаемся к не-nullable viewState и передаём туда загруженные данные. Моху прикопает результат и отправит View, когда оно будет активно. При пересоздании View команда может быть повторена (зависит от стратегии) при этом заново данные не будут запрашиваться.
Решение без Moxy
Обращаемся к View по nullable-ссылке. Эта ссылка зануляется при пересоздании View. Если к моменту завершения запроса view не приаттачена, то данные потеряются.
Возможное решение проблемы.
Прикапывать данные в какой-то сущности, которая не связана с View (например, в интеракторе). При onResume() запрашивать прихранённые данные и отображать их.
Минусы решения.
Задача: сохранение состояния отображения
Часто возникает ситуация, когда нам нужно хранить какое-то состояние отображения.
Решение с Moxy
Храним состояние в presenter. Presenter выживает при пересоздании View, поэтому там можно хранить состояние. Можно хранить данные любых типов, в т.ч. ссылки на интерфейсы, например.
Решения без Moxy
Можно хранить состояние в savedInstanceState или аргументах фрагмента.
Минусы решения
Можно хранить данные на уровне бизнес-логики, в специальном классе (пусть этот класс будет отвечать только за state конкретной View и не будет использоваться где-то ещё).
Минусы решения
Задача: обмен данными между экранами
Часто бывает нужно результат действий на одном View отобразить на другом View.
Решение с Moxy
Обмен данными между экранами осуществляется так же как и асинхронный запрос. Разве что подписка на изменения идёт на subject или channel в интеракторе, в который presenter другой View кидает изменённые данные. Подписка в Presenter.onFirstViewAttach(), отписка — в Presenter.onDestroy().
Решения без Moxy
Задача: инициализация чего-либо, связанного с экраном
Часто бывает нужно проинициализировать что-то, связанное с данным экраном. Например, какой-то внешний компонент.
Решение с Moxy
Проинициализировать компонент можно в Presenter.onFirstViewAttach() и освободить в Presenter.onDestroy() — это единственные коллбэки, о которых нам нужно задумываться.
Presenter.onFirstViewAttach() — вызывается при самом первом создании View,
Presenter.onDestroy() — вызывается при окончательном уничтожении View.
Решение без Moxy
Можно проинициализировать в onCreate() и освободить в onDestroy() активити или фрагмента.
Минусы решения
Задача: показ AlertDialog
Особенностью использования AlertDialog является то, что он пропадает при пересоздании View. Поэтому при пересоздании View нужно заново отображать AlertDialog.
Решение с Moxy
Выбрать правильную стратегию. Диалог сам перепокажется при пересоздании View.
Решения без Moxy
Особенности использования Moxy
Moxy позволяет избежать boilerplate кода при использовании MVP в android-приложении. Но, как и любой другой библиотекой, Moxy нужно научиться пользоваться. К счастью, использовать Moxy легко. Далее описаны моменты, на которые нужно обратить внимание.
Либо можно использовать кастомную стратегию с тегами. Например, как описано тут: https://habr.com/ru/company/redmadrobot/blog/341108/
Это нужно чтобы команда показа прогресса не вызвалась больше после команды скрытия прогресса.
Так делать нельзя. Нужно чтобы функция @ProvidePresenter гарантированно создавала новый инстанс presenter. Здесь при пересоздании фрагмента появится новый инстанс presenter. А Moxy будет работать со старым, т.к. функция providePresenter() вызывается только один раз.
Как вариант, можно в providePresenter() просто создать новый инстанс presenter:
Это не очень удобно — ведь придётся инжектить в фрагмент все зависимости этого presenter.
Можно из компонента dagger сделать метод для получения presenter и позвать его в providePresenter():
Важно, чтобы провайдер presenter’а и сам presenter не были помечены аннотацией Singleton.
В последних версиях Moxy можно использовать делегаты kotlin:
Ещё можно заинжектить через Provider:
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 рассказывает о том, как пользоваться библиотекой.