Чтобы проще было развивать и поддерживать код продукта, сложную логику можно разбить на конечное множество состояний и описать правила переходов между ними. В итоге мы получаем конечный автомат. Часть бизнес-логики, описывающая смену состояний в MVI-архитектуре, может быть реализована в виде конечного автомата. Это даст возможность представить вашу логику в виде графа переходов для последующей визуализации и анализа. Мы написали и выложили в опенсорс MVI-библиотеку на Kotlin — VisualFSM, которая умеет по исходному коду строить визуализацию вашей системы, что позволит быстрее понимать сложные бизнес-процессы, упрощать поиск ошибок, добавлять новую функциональность и проводить рефакторинг. Под катом я расскажу подробнее о нашем подходе, о том, как устроена библиотека, и как начать ее использовать. MVI (Model-View-Intent) — архитектурный паттерн, который следует подходу "однонаправленный поток данных" (unidirectional data flow). Данные передаются от Model к View в одном направлении.
В VisualFSM Intent реализуется в виде действия (Action), в котором описываются возможные переходы состояний (Transition).  FSM (Finite-state machine, конечный автомат) — абстрактная сущность, которая может находиться только в одном из конечного количества состояний в определённый момент. Она может переходить из одного состояния в другое в ответ на входные данные.  Входной алфавит FSM — это объекты действий (Action). Выходным алфавитом являются объекты состояний (State). В каждый момент времени FSM находится в одном из конечного множества состояний (State). MVI-архитектура хорошо сочетается с абстракцией FSM, в MVI у модели один вход и один выход, так же как в FSM. Если соединить выход View со входом FSM и наоборот то можно объединить две концепции, сделать это удобно позволяет библиотека VisualFSM.  Один набор классов Action и State используется для реализации MVI и описания FSM. Анализ исходного кода и построение графа выполняется с помощью рефлексии и реализован отдельным модулем, что даёт возможность подключить его только к тестовой среде. Не требуется написания отдельных конфигураторов для FSM, достаточно добавлять новые классы State и Action – они становятся частью графа состояний и переходов FSM.  Визуализация позволяет быстрее понимать сложные бизнес-процессы, упрощает поиск ошибок, помогает добавлять новую функциональность и проводить рефакторинг. Если нужно добавить или убрать дополнительный диалог или экран, то, смотря на схему, проще понять, какие переходы и состояния нужно изменить. Кроме того, глядя на схему, можно судить об оптимальности схемы, планировать объединение или наоборот выделять части в отдельные FSM. В тестировании — полная схема состояний и переходов помогает тестировщику описать сценарии проверки. Тестовые инструменты дают возможность выполнять такие распространенные проверки, как проверка на достижимость всех состояний и проверка множества терминальных состояний (для выявления незапланированных тупиковых состояний). Также можно получить граф в виде списка ребер или словаря смежности для реализации прочих проверок в unit-тестах. Сопоставив полный граф переходов и граф фактических переходов, выполненных на CI в процессе прохождения функциональных тестов, можно выявить переходы и состояния, не покрытые автотестами. Подробнее о том, как мы выполняем такой анализ, расскажем в отдельной статье. Чтобы не пропустить, подписывайтесь на @visualfsm в Telegram. Запись фактических переходов во время выполнения Ui тестов можно произвести в файл с помощью реализации TransitionCallbacks интерфейса.  В библиотеке нет SingleEvent шины, все transform функции чистые. Благодаря этому нельзя отправить или обработать Event, не привязанный к текущему состоянию. Подробнее про недостатки SingleEvents можно почитать в статье Android DevRel Manuel Vivo: ViewModel: One-off event antipatterns  Если необходимо отобразить тост, snackbar или диалог, это рекомендуется сделать через изменение состояния (см. Login.snackBarMessage в примере).  AsyncWorker запускает асинхронный запрос или останавливает его, если ему по подписке придёт соответствующий State. Как только запрос завершится успешно или с ошибкой, результат необходимо передать в FSM, вызвав Action, и в FSM будет установлен новый State. Асинхронная работа может быть представлена отдельными состояниями – благодаря этому мы имеем единый набор состояний, которые выстраиваются в ориентированный граф. Объект AsyncWorker упрощает обработку состояний, в которых выполняется асинхронная работа. Есть два способа описания состояния, в котором ведется асинхронная работа: State – интерфейс-метка для обозначения классов состояний. Пример реализации State — AuthFSMState. Action — базовый класс действия, является входным объектом для FSM и описывает правила переходов в другие состояния, используя классы Transition. В зависимости от текущего State у FSM и заданного предиката (функции predicate) конструируется State, в который нужно перейти.  Пример реализации Action — actions. Transition — базовый класс перехода, реализуется как inner class в Action. Для
каждого Transition нужно указать два generic параметра <FROM : State, TO : State>:  Классам наследникам Transition необходимо реализовать функцию transform, а при наличии ветвления переопределить функцию predicate. AsyncWorker управляет запуском и остановкой асинхронной работы.  Подробнее о конфигурации AsyncWorker и доступных стратегий запуска и остановки операций в документации. Пример реализации AsyncWorker — AuthFSMAsyncWorker. Feature — фасад к FSM, предоставляет подписку на State и принимает Action для обработки. Пример реализации Feature — AuthFeature. TransitionCallbacks предоставляет функции обратного вызова для сторонней логики. Их удобно использовать для логгирования, записи бизнес метрик или отладки: Пример использования инструментов — AuthFSMTests. Для сокращения шаблонного кода в реализациях Action классов мы используем KSP кодогенерацию. Генерируемым классом является TransitionsFactory для FSM, в котором инициализируются списки переходов для каждого Action.  Для возобновления работы FSM с определенного состояния его необходимо передать в конструктор Feature. Если вы используете DI, то модуль, содержащий объекты FSM, должен инициализироваться после вызова onCreate Activity или Fragment, когда становится доступным savedInstanceState Bundle, в котором было сохранено состояние. Объект State при этом должен реализовать интерфейс Parcelable и быть передан в Bundle метода onSaveInstanceState. Пример Koin DI модуля, зависимого от сохраненного состояния — Modules.kt. Android приложение (Kotlin Coroutines, Jetpack Compose)
KMM (Android + iOS) приложение (Kotlin Coroutines, Jetpack Compose, SwiftUI) Подключение библиотеки в проект описано в Quickstart Библиотека используется в проекте Контур.Маркет Касса. В разработке плагин для IntelliJ IDEA и Android Studio для визуализации диаграммы состояний в IDE и навигации по классам FSM из диаграммы. Подробнее о проекте, в котором родилась идея библиотеки, и историю о первом неудачном подходе можно посмотреть в записи доклада Mobius Spring 2022: Василий Рылов — MVI и State Machine — визуализация и анализ диаграммы состояний с помощью VisualFSM. О новых релизах мы рассказываем в Telegram канале. Обсудить вопрос применения библиотеки или проблему, с которой вы столкнулись, можно в чате поддержки библиотеки. Software Developer