Стратегії в Moxy (частина 1)

Ця поведінка реалізована за рахунок сутності ViewState, яка проксує виклики методів між Presenter і View, при цьому зберігаючи деякі з них у черзі на підставі спеціальних стратегій. При перетворенні View викликаються в повному обсязі методи, лише ті, які у черзі нині.
Черга команд (методи, які викликаються у 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:
- Команда (2) додається до кінця черги ViewState
- Команда (2) застосовується до View, якщо вона перебуває в активному стані
- До View послідовно застосовуються команди з черги ViewState
Ця стратегія відрізняється від AddToEndStrategy тим, що в черзі ViewState може бути тількиодин екземпляр кожної команди, позначеної цією стратегією.

Під час виклику презентатором команди (2) зі стратегією AddToEndSingleStrategy:
- Команда (2) додається до кінця черги ViewState.
- У випадку, якщо в черзі вже була команда (2), стара команда видаляється з черги
- Команда (2) застосовується до View, якщо вона перебуває в активному стані
- До View послідовно застосовуються команди з черги ViewState
Ця стратегія за своїм принципом роботи схожа на AddToEndSingleStrategy. Відмінність полягає в тому, що при попаданні в ViewState команди з даною стратегією черга команд очищається повністю.

Під час виклику презентатором команди (1) зі стратегією SingleStateStrategy:
- Повністю очищується черга команд
- Команда (1) додається до кінця черги ViewState.
- Команда (1) застосовується до View, якщо вона перебуває в активному стані
- До View послідовно застосовуються команди з черги ViewState
Skip стратегія не змінює стека ViewState. Команда застосовується до View, тільки якщо воно перебуває в активному стані.

Команда поводиться по-різному, залежно від наявності в'ю в активному стані.
а)У разінаявностіView в активному стані:
При викликі презентером команди (4) зі стратегією SkipStrategy, у випадкунаявностіView в активному стані:
- Команда застосовується до View
- Черга команд залишається незмінною
- До Viewпослідовно застосовуються команди з черги ViewState
При викликі презентатором команди (4) зі стратегією SkipStrategy, у разі відсутностіView в активному стані:
- Команда не застосовується до View
- Черга команд залишається незмінною
- До View послідовно застосовуються команди з черги ViewState
Ця стратегія дуже схожа на SkipStrategy. Відмінність полягає в тому, що за відсутності view в активному стані команда чекає її появи, а потім застосовується рівно один раз.

Команда поводиться по-різному, залежно від наявності в'ю в активному стані.
а)У разінаявностіView в активному стані:
При виклик презентатором команди (4) зі стратегією OneExecuteStrategy, у випадкунаявностіView в активному стані (Поведінка повністю аналогічна SkipStretegy):
- Команда застосовується до View
- Черга команд залишається незмінною
- До View послідовно застосовуються команди з черги ViewState
При викликі презентатором команди (4) зі стратегією OneExecuteStrategy, у разі відсутностіView в активному стані:
- Команда (4) додається до кінця черги ViewState
- До View послідовно застосовуються команди з черги ViewState
- Під час виконання команди (4) вона видаляється із черги команд. При подальшому перестворенні Viewкоманда (4) не викликається.
Відсутність явного вказівки стратегії методу веде до її автоматичного висновку виходячи з стратегії всього інтерфейсу і стратеги за замовчуванням. В даний час стандартною стратегією є AddToEndStrategy.
Область застосування команд
У розділі ми зібрали типові випадки застосування команд.
AddToEndStrategy— застосовується у разі, коли потрібно послідовно застосувати кілька команд, які мають бути повторно застосовані при перестворенні View.
Приклад: Екран організації складається з трьох блоків: загальна інформація, акції, історія покупок. Дані частини можуть бути завантажені та відображені на екрані асинхронно. Після перестворення важливо відобразити інформацію у всіх блоках. Усі методи будуть позначені стратегією AddToEndStrategy
AddToEndSingleStrategy- застосовується у випадку, коли команда повинна застосовуватися при перестворенні view не більше одного разу.
Приклад: На екрані є індикатор завантаження, видимість якого змінюється викликом View: toggleLoading(visible: Boolean).
Спойлер: тут і далі приклади коду наведені на 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 зручно виконувати за допомогою цієї стратегії. У цьому випадку ми будемо мати гарантовано один запуск.
OneExecuteStrategy та SkipStrategy дуже схожі, будьте уважні при їх використанні.
Як уникнути проблем через стратегії?
Використовуючи стратегії, ви можете зіткнутися з двома видами проблем:
Проблема 1. Зайві команди у черзі
При використаннілишеAddToEndStrategy, яка є стандартною стратегією, черга поступово збільшується. Це може викликати:
- Freeze ui в момент прикріплення View до Presenter
- Переповнення пам'яті
Намагайтеся мінімізувати величину черги команд, виставляючи стратегії.
Проблема 2.Неправильний стан при перетворенні view
Найчастіше додаток вимагає лише портретну орієнтацію, внаслідок чого при розробці вона фіксується, проте перестворення в'ю відбувається не тільки при зміні орієнтації, але й за інших змін конфігурації.
Під час розробки залишати можливість зміни орієнтації програми. Це допоможе вам контролювати не тільки правильність застосування стратегій, але й інші side-ефекти зміну конфігурації пристрою.
У частині 2 ми розглянемо механізм роботи стратегій зсередини і як створювати кастомні стратегії.
Нехай код завжди виконується так, як ви його проектували!