Наш фреймворк Moxy получил широкое распространение в разработке под Android.
Он обеспечивает реализацию MVP паттерна при работе с Activity, Fragment и View, полностью отделяя “callback hell” их жизненного цикла от презентера.
Данное поведение реализовано за счет сущности ViewState, которая проксирует вызовы методов между Presenter и View, при этом сохраняя некоторые из них в очереди на основании специальных стратегий. При пересоздании View вызываются не все методы, а только те, которые находятся в очереди на данный момент.
В данной статье мы расскажем, как работают стратегии, предоставляемые “из коробки”, и в каких случаях стоит применять каждую из них.
Принципы работы 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 — выполнить команду и добавить команду в конец очереди
AddToEndSingleStrtegy — выполнить команду, добавить ее в конец очереди и удалить все ее предыдущие экземпляры
SingleStateStrtegy — выполнить команду, очистить очередь и добавить в нее команду
SkipStrtegy — выполнить команду
OneExecuteStrtegy — выполнить команду при первой возможности
Рассмотрим подробнее как они работают и когда их стоит применять.
AddToEndStrtegy
AddToEndStrategy — самая простая стратегия, представляющая собой добавление команды в конец очереди
При вызове презентером команды (2) со стратегией AddToEndStrtegy:
- Команда (2) добавляется в конец очереди ViewState
- Команда (2) применяется ко View, если оно находится в активном состоянии
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
AddToEndSingleStrtegy
Данная стратегия отличается от AddToEndStrtegy тем, что в очереди ViewState может находиться только один экземпляр каждой команды, помеченной данной стратегией
При вызове презентером команды (2) со стратегией AddToEndStrtegy:
- Команда (2) добавляется в конец очереди ViewState.
- В случае, если в очереди уже находилась команда (2), старая команда удаляется из очереди
- Команда (2) применяется ко View, если оно находится в активном состоянии
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
SingleStateStrtegy
Данная стратегия по своему принципу работы похожа на AddToEndSingleStrtegy. Отличие заключается в том, что при попадании во ViewState команды с данной стратегией, очередь команд очищается полностью.
При вызове презентером команды (1) со стратегией SingleStateStrtegy:
- Полностью очищается очередь команд
- Команда (1) добавляется в конец очереди ViewState.
- Команда (1) применяется ко View, если оно находится в активном состоянии
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
SkipStrtegy
Skip стратегия не меняет стека ViewState. Команда применяется ко View, только в случае, если оно находится в активном состоянии.
Команда ведет себя по-разному, в зависимости от наличия вью в активном состоянии.
а) В случае наличия View в активном состоянии:
При вызове презентером команды (4) со стратегией SkipStrtegy, в случае наличия View в активном состоянии:
- Команда применяется ко View
- Очередь команд остается неизменной
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
б) В случае отсутствия View в активном состоянии:
При вызове презентером команды (4) со стратегией SkipStrtegy, в случае отсутствия View в активном состоянии:
- Команда не применяется ко View
- Очередь команд остается неизменной
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
OneExecuteStrtegy
Данная стратегия очень похожа на SkipStrategy. Отличие заключается в том, что при отсутствии view в активном состоянии, команда дожидается ее появления, а затем применяется ровно один раз.
Команда ведет себя по-разному, в зависимости от наличия вью в активном состоянии.
а) В случае наличия View в активном состоянии:
При вызове презентером команды (4) со стратегией OneExecuteStrtegy, в случае наличия View в активном состоянии (Поведение полностью аналогично SkipStretegy):
- Команда применяется ко View
- Очередь команд остается неизменной
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
б) В случае отсутствия View в активном состоянии:
При вызове презентером команды (4) со стратегией OneExecuteStrtegy, в случае отсутствия View в активном состоянии:
- Команда (4) добавляется в конец очереди ViewState
При появлении View в активном состоянии:
- Ко View последовательно применяются команды из очереди ViewState
- При выполнении команды (4) она удаляется из очереди команд. При последующем пересоздании View команда (4) не вызовется
Отсутствие стратегии у метода
Отсутствие явного указания стратегии у метода ведет к ее автоматическому выводу на основании стратегии для всего интерфейса и стратеги по умолчанию. В настоящее время стратегией по умолчанию является AddToEndSingleStrategy.
Область применения команд
В данном разделе мы собрали типичные случаи применения команд.
AddToEndStrtegy — применяется в случае, когда нужно последовательно применить несколько команд, которые должен быть повторно применены при пересоздании View.
Пример: Экран организации состоит из 3-х блоков: общая информации, акции, история покупок. Данные части могут быть загружены и отображены на экране асинхронно. После пересоздания важно, отобразить информацию во всех блоках. Все методы будут помечены стратегией AddToEndStrtegy
AddToEndSingleStrtegy — применяется в случае, когда команда должна применяться при пересоздании view не более одного раза.
Пример: На экране есть индикатор загрузки, видимость которого меняется вызовом метода View: toggleLoading(visible: Boolean).
Cпойлер: здесь и далее примеры кода приведены на Kotlin.
Данный метод будет иметь стратегию AddToEndSingleStrtegy.
SingleStrategy — применяется в случае, если нам не важен результат команд отработавших до нее.
Пример: Данные экрана профиля подгружаются из сети. Экран имеет 3 взаимоисключающие состояния: загрузка, отображение данных и экран—заглушка. Соответственно View имеет методы showLoading(), showData() и showStub().
Все три команды будут иметь стратегию SingleStateStrategy.
SkipStrtegy — используется в случае, если нам необходимо выполнить некоторое действие, прямо сейчас и только в случае, если имеется View в активном состоянии.
Пример 1: Имеется экран редактирования данных пользователя. При нажатии кнопки сохранить, отображается индикатор загрузки посредством вызова команды toggleLoading(show = true). При неуспешной загрузке экран возвращается в исходное состояние командой toggleLoading(show = false) и отображается SnackBar с информацией об ошибке командой showLoadingError(). Последняя команда будет иметь стратегию SkipStrategy, т.к. результат ее выполнения нужен только при активном экране и не должен сохраняться при смене конфигурации.
Пример 2: Старт анимации после какого-то действия пользователя. Команду имеет смысл применять только один раз, причем при отсутствии активной вью не требуется сохранение ее в очередь.
OneExecuteStrtegy — используется в случае, если нам необходимо выполнить некоторое действие при первом появление View в активном состоянии.
Пример 1: Открытие следующего экрана. Запуск нового Activity, Fragment или FragmentDialog удобно выполнять посредством данной стратегии. В этом случае мы будем иметь гарантированно 1 запуск.
OneExecuteStrategy и SkipStrategy очень похожи, будьте внимательны при их использовании.
Как избежать проблем из-за стратегий?
При использовании стратегий вы можете столкнуться с двумя видами проблем:
Проблема 1. Лишние команды в очереди
При использовании только AddToEndStrategy, которая является стратегией по умолчанию, очередь постепенно увеличивается. Это может вызывать:
- Freeze ui в момент прикрепления View к Presenter
- Переполнение по памяти
Решение:
Старайтесь минимизировать величину очереди команд, посредством выставления стратегий.
Проблема 2. Неправильное состояние при пересоздании view
Зачастую приложение требует только портретную ориентации, вследствии чего при разработке она фиксируется, однако пересоздание вью происходит не только при смене ориентации, но и при других сменах конфигурации.
Решение:
При разработке оставлять возможность смены ориентации приложения. Это поможет вам контролировать не только правильность применения стратегий, но и остальные side-эффекты смену конфигурации устройства.
Заключение
Стратегии в Moxy являются довольно гибким инструментом, неаккуратное использование которого может повлечь логические ошибки. Надеемся что данная статья сделает использование Moxy более прозрачным и очевидным.
В следующей статье мы рассмотрим, как создавать кастомные стратегии.
Пусть ваш код всегда выполняется так, как вы его проектировали!
Материалы по теме
- Android without Lifecycle: MPVsV approach with Moxy
- Moxy — реализация MVP под Android с щепоткой магии
- MVP на стероидах: заставляем робота писать код за вас
Автор: REDMADROBOT