- MVP на стероидах: заставляем робота писать код за вас
- Структура проекта
- Настройка шаблонов
- Использование шаблонов
- Что в итоге
- Стратегии в Moxy (Часть 2)
- Зачем нужны кастомные стратегии
- Механика работы команд Moxy
- Создаем кастомную стратегию
- Предназначение..
- Moxy Strategy plugin
- Inspection
- Intention
- isAvailable
- invoke
- Итого
MVP на стероидах: заставляем робота писать код за вас
Структура проекта
Для того чтобы начать кодогенерацию, нужно зафиксировать структуру проекта.
- model
- presentation
- presenter
- view
- ui
- activity
- fragment
Содержимое пакетов presenter, view, activity и fragment, в свою очередь, делится на логические модули. Часто такими модулями выступают разделы приложения (к примеру, intro, offers, feed). Ниже — пример структуры проекта с двумя Activity (CarActivity и HomeActivity) и одним фрагментом (CarDetailsFragment)
Наша цель — научиться генерировать эти классы
Настройка шаблонов
Как создавать шаблоны в Android Studio можно прочитать в статье от Fi5t про Тотальную шаблонизацию
Добавляем в проект шаблоны для Moxy:
- Скачиваем шаблоны с Github либо по ссылке
- Копируем содержимое архива в ANDROID_STUDIO_DIR/plugins/android/lib/templates/activities
- Перезапускаем Android Studio, чтобы изменения вступили в силу
Настраиваем hot keys для быстрого доступа к шаблонам:
- Открываем настройки-> Keymap
- В поисковом окне вводим Moxy
- Добавляем комбинации клавиш (я использую Alt + A для активити и Alt + F для фрагмента)
Использование шаблонов
- Выделяем корневой пакет и нажимаем Alt + A.
- В поле Activity Name пишем «MyFirstMoxyActivity»
Остальные поля наберутся сами.
Далее меняем в поле Package Name слово blank на имя подпакета и нажимаем Finish. Ваш пакет классов готов! Стоит отметить, что при обновлении Android Studio может снести все кастомные шаблоны. В этом случае придется их импортировать заново.
Что в итоге
Концепция MVP подразумевает разделение логики приложения на слои и, как следствие, увеличение кодовой базы. Использование шаблонов и кодогенерации максимально избавляет вас от boilerplate, предохраняет от случайных ошибок и позволяет сосредоточиться на бизнес-логике. Пусть код за вас пишет робот!
Источник
Стратегии в Moxy (Часть 2)
В части 1 мы разобрались, для чего нужны стратегии в Moxy и в каких случаях уместно применять каждую из них. В этой статье мы рассмотрим механизм работы стратегий изнутри, поймем, в каких случаях нам могут понадобиться кастомные стратегии, и попробуем создать свою собственную.
Зачем нужны кастомные стратегии
Зачем вообще Moxy поддерживает создание
пользовательских стратегий? При проектировании библиотеки мы (I) старались учесть все возможные случаи, и встроенные стратегии практически на сто процентов их покрывают. Однако в некоторых случаях может потребоваться больше власти над ситуацией, и мы не хотели вас ограничивать. Рассмотрим один из таких случаев.
Презентер отвечает за выбор бизнес-ланча, который состоит из бургера и напитка.
Команды в зависимости от функции у нас делятся на следующие типы:
- кастомизируют бургер (добавить/удалить сыр, выбрать ржаную/пшеничную булку и т.д.);
- кастомизируют напиток (выбрать количество ложек сахара, добавить/удалить лимон и т.д);
- оповещают о том, что заказ отправлен.
Итак, мы хотим уметь отдельно управлять очередями команд для бургера и для напитка. Стратегий по умолчанию для этого не хватит, дополнительно нам нужны бургерные и напиточные! Давайте их изобретем, но для начала разберемся, как вообще устроен механизм применения команд и на что влияют стратегии.
Механика работы команд Moxy
Начнем издалека: в конструкторе презентера создается ViewState, все команды проксируются через него. ViewState содержит очередь команд — ViewCommands (класс, который отвечает за список команд и стратегий) и список View. В списке View может содержаться как несколько View, если вы используете презентер типа Global или Weak, так и ни одного (в ситуации, когда у вас фрагмент ушел в бэк стэк).
Между нами говоря, не стройте на их основе архитектуру, так как они залезают не в свою зону ответственности. Шарить данные между экранами лучше при помощи общих сущностей типа интеракторов в чистой архитектуре. О чистой архитектуре есть неплохая статья
Для начала разберемся, что же представляет собой стратегия:
Это интерфейс с двумя методами: beforeApply и afterApply. Каждый метод на вход принимает новую команду и текущий список команд, который и будет меняться (или останется без изменений) в теле метода. У каждой команды мы можем получить тэг (это строка, которую можно указать в аннотации StateStrategyType) и тип стратегии (см. листинг ниже). Как именно менять список решаем, опираясь только на эту информацию.
Давайте поймем, когда у нас будут вызываться данные методы. Итак, у нас есть интерфейс SimpleBurgerView, который умеет только добавлять немного сыра(II).
Рассмотрим, что происходит при вызове метода toggleCheese у сгенерированного класса LaunchView$$State (см. листинг):
1) Создается команда ToggleCheeseCommand (см. листинг ниже)
2) Вызывается метод beforeApply для класса ViewCommands для данной команды. В нем мы получаем стратегию и вызываем ее метод beforeApply. (см. листинг ниже)
Ура! Теперь мы знаем, когда выполняется метод beforeApply у стратегии: сразу же после соответствующего вызова метода у ViewState и только тогда. Продолжаем погружение!
В случае если у нас есть View:
3) Им поочередно проксируется метод toggleCheese.
4) Вызывается метод afterApply для класса ViewCommands для данной комнады. В нем мы получаем стратегию и вызываем ее метод afterApply.
Однако afterApply вызывается не только в этом случае. Он также будет вызван в случае аттача новой View. Давайте рассмотрим этот случай. При аттаче View вызывается метод attachView(View view)
Метод attachView(View view) вызывается из метода onAttach() класса MvpDelegate. Тот в свою очередь вызывается довольно часто: из методов onStart() и onResume(). Тем не менее библиотека гарантирует, что afterApply вызовется один раз для приаттаченой вью (см. листинг ниже).
1) Вью добавляется в список.
2) Если ее в этом списке не было, вью переводится в состояние StateRestoring
.
Это даёт возможность активити/вью/фрагменту понять, что состояние восстанавливается. У презентера есть метод isInRestoreState(). Этот механизм необходим для того, чтобы не выполнять некоторые действия дважды (например, старт анимации для перевода вью в нужное состояние). Это единственный метод presenter, который возвращает не void. Данный метод принадлежит presenter, т.к. view и mvpDelegate могут иметь несколько презентеров и помещение их в эти классы привело бы к коллизиям.
3) Далее происходит восстановление состояния
Стоит отметить, что метод afterApply может вызываться несколько раз. Обратите на это внимание, когда будете писать свои кастомные стратегии.
Мы познакомились с тем, как работают стратегии, пришло время закрепить навыки на практике.
Создаем кастомную стратегию
Схема работы
Итак, для начала давайте поймем, какую именно стратегию мы хотим получить. Договоримся об обозначениях.
Данная схема похожа на схему из первой части, однако в ней есть важное отличие -— появилось обозначение тэга. Отсутствие тэга у команды обозначает, что мы его не указывали и он принял значение по умолчанию — null
Мы хотим реализовать следующую стратегию:
При вызове презентером команды (2) со стратегией AddToEndSingleTagStrategy:
- Команда (2) добавляется в конец очереди ViewState.
- В случае, если в очереди уже находилась любая другая команда с аналогичным тэгом, она удаляется из очереди.
- Команда (2) применяется ко View, если оно находится в активном состоянии.
При пересоздании View:
- Ко View последовательно применяются команды из очереди ViewState
Реализация
1) Реализуем интерфейс StateStrategy. Для этого переопределяем методы beforeApply и afterApply
2) Реализация метода beforeApply будет очень похожа на реализацию аналогичного метода в классе AddToEndSingleStrategy.
Мы хотим удалить из очереди абсолютно все команды с данным тегом, т.е. удаляться будут даже команды с другой стратегией, но аналогичным тегом.
Поэтому мы вместо строчки entry.class == incomingCommand.class будем использовать entry.tag == incomingCommand.tag
В Kotlin == равносильно .equals в Java
Также нам необходимо убрать строку break, так как в отличие от AddToEndSingleStrategy у нас в очереди могут появиться несколько команд для удаления.
3) Реализацию метода afterApply оставим пустой, так как у нас нет необходимости менять очередь после применения команды.
Итак, что у нас получилось:
Вот и все, осталось проиллюстрировать, как мы будем использовать стратегию (см. листинг ниже).
Полный пример кода можно посмотреть в репозитории Moxy. Напоминаю, что это сэмпл и решения в нем приведены сугубо для иллюстрации функционала фреймворка
Предназначение..
Для чего еще можно использовать кастомные стратегии:
1) склеивать предыдущие команды в одну;
2) менять порядок выполнения команд, если команды не коммутативны (a•b != b•a);
3) выкидывать все команды, которые не содержат текущий tag;
4) ..
Если вы часто используете команды, а их нет в списке дефолтных — пишите, обсудим их добавление.
Обсудить Moxy можно в чате сообщества
Ждем замечаний и предложений по статье и библиотеке 😉
(I) здесь и далее «мы» — авторы Moxy: Xanderblinov, senneco и все ребята из сообщества, которые помогали советами, замечаниями и пулреквестами. Полный список контрибьютеров можно посмотреть здесь
(II) Код в листингах написан на Kotlin
Источник
Moxy Strategy plugin
Иногда простые вещи очень утомляют, особенно когда их необходимо делать постоянно. Одна из таких вещей при работе с фреймворком Moxy — это добавление стратегий к функциям. Для ускорения этого процесса был написан плагин, который по «alt+enter» предоставляет выбор стратегии если ее нет или диалог с заменой на другую стратегию. Те, кто хочет узнать как это работает, добро пожаловать под кат.
Когда я только начинал изучать Moxy, одной из сложностей было запомнить названия стратегий и не забывать об их существовании. После практики с ними эта проблема ушла, но все равно было желание каким то образом это оптимизировать.
В этот момент я заинтересовался созданием плагинов, и adev_one подсказал мне, что плагин как раз и может решить такую проблему.
Inspection
Итак, мы написали функцию и хотим добавить стратегию. В этот момент нам на помощь приходит code inspections. Они анализируют код текущего файла при помощи PSI (Program Structure Interface), и если что то не так, подсвечивают код красным или серым, и по alt+enter предлагают возможные исправления.
Официальная документация по разработке code inspections.
PSI — это структура, в которой у каждого выражения, ключевого слова и т.д. есть свой аналог, который содержит информацию о нем, его parent и child. Для того, чтобы понять какой класс соответствует необходимой конструкции языка, есть хороший плагин Psi viewer plugin.
Рассмотрим для примера с помощью него структуру интерфейса с функцией.
Видим, что интересующий нас класс это KtNamedFunction.
Рассмотрим создание Inpection.
Как создавать проект и не только хорошо расписано в цикле статей. Дополнительно понадобятся зависимости в файле plugins.xml
и в файле build.gradle
Перейдем к созданию inpection.
Для начала необходимо наследовать AbstractKotlinInspection
В нем нам понадобится переопределить следующую функцию:
В ней мы создаем Visitor, который будет анализировать текущий код файла и добавлять ошибки и предупреждения.
Хорошей документации по Visitor я не нашел, но когда смотрел плагины kotlin наткнулся на очень удобные функции-фабрики для создания Visitor. Для анализа KtNamedFunction есть следующая фабрика:
В лямбде нужно проанализировать PSI: выбрана ли функция без аннотации и является ли она частью интерфейса, который реализует интерфейс MvpView. Когда все условия выполнены, регистрируем ошибку с помощью ProblemsHolder.
В итоге выглядит это вот так:
Что такое AddStrategyFix? Это объект, который наследует LocalQuickFix. Он отвечает за исправления кода и выполнится, когда будет выбран из предложенных пунктов:
getFamilyName — определяет название пункта, который будет в диалоге.
В applyFix очевидно нужно выполнить исправление. В нашем случае добавить аннотацию.
Осталось зарегистрировать Inspection в plguin.xml, (похоже Activity в Manifest)
В итоге получаем:
Так же автоматически добавляются импорты аннотации StateStrategyType и выбранной стратегии.
Для стратегии AddToEndSingleTagStrategy дополнительно сделал добавление второго аргумента
tag и перенос курсора на него.
Intention
Функцию написали, стратегию быстро добавили. Передумали и решили заменить ее на другую.
Теперь наш помощник это Intention.
Принцип работы аналогичен с Inspection, но без какого либо выделения кода и анализируется только текущий выбранный элемент под кареткой. Как использовать уже имеющиеся intention. Официальная документация по разработке.
Рассмотрим создание Intention.
Необходимо наследовать PsiElementBaseIntentionAction :
getText — определяет отображаемое название
getFamilyName — нужен для иерархии intention и отображения в настройках
isAvailable — в этой функции исходя из текущего элемента под курсором, необходимо определить будет ли добавлен intention в текущий список или нет.
invoke — вызывается, когда мы выбрали данный intention из списка
Рассмотрим подробнее isAvailable и invoke:
isAvailable
В этой функции нужно как можно раньше определить, что intention не нужно добавлять и не анализировать дальше. Это увеличит производительность.
Так же текущий выбранный элемент может быть child текущий функции, потому необходимо получить саму функцию. Остальные проверки аналогичны.
invoke
Поскольку добавление отдельного Intention для каждой стратегии не выгодно по производительности, необходимо показать еще один диалог по нажатию на один пункт.
Под капотом наша любимая IDE использует swing. Соответственно, кто хорошо с ним знаком, может создавать разнообразный интерфейс. Я пошел другим путем и использовал простой билдер для диалога выбора — JBPopupFactory
Однако, после создания диалога контекст сменяется на другой, который уже не может изменять код и при попытки сделать это выдает ошибку. Чтобы это исправить дополнительно оборачиваем действие по замене стратегии в функцию WriteCommandAction.runWriteCommandAction(project) <>
Так же регистрируем intention в plugin.xml
В итоге получаем следующий функционал:
Итого
С этим плагином решается проблема запоминания названий стандартных стратегий. Их список всегда виден. Если принцип работы стратегии поначалу может быть не понятен, возможно вставить её и перейти к исходному коду, где есть документация. Также в общем улучшается удобство работы со стратегиями Moxy.
Создание плагинов раньше для меня казалось очень сложным и непонятным. В основном из-за отсутствия примеров и недостаточности документации на официальном сайте. Это и подтолкнуло меня написать эту статью. Надеюсь создание плагинов стало для вас чуть более понятным. Буду рад конструктивной критике и обратной связи.
Источник