Skip to content

Latest commit

 

History

History
89 lines (75 loc) · 10.4 KB

README.md

File metadata and controls

89 lines (75 loc) · 10.4 KB

Surf MVI-flow

Современная библиотека для организации слоя представления. Является продолжением и развитием идей библиотеки Surf-MVI Подход во многом черпал вдохновение из Redux, Flux и MVI Android.

Внимание! Модуль находится в стадии активной разработки!

Общее описание

Основная идея в том, что у экрана есть единое состояние. А логика изменения этого состояния отделена от логики запросов в сеть или базу данных. Изменять состояние экрана не может никто, кроме Reducer. Ответственность за трансформацию потока событий лежит на Middleware. Таким образом, вся логика, приводящая к изменению состояния UI, концентрируется в одном месте, и она отделена от логики запросов к серверу, в базу данных, получению каких-то обновлений с других экранов и тд. А вью остается только отрисовывать это состояние и отправлять события взаимодействия с пользователем.

Основные преимущества

Любые действия пользователя, вызовы методов жизненного цикла, получение данных с сервисного слоя и отправка данных на UI рассматриваются как единая сущность: событие. Все события, совершаемые на всех стадиях жизни экрана, проходят через единую шину: хаб. Таким образом, достигается полная абстракция классов внутри экрана, и устранение связей между ними.

К тому же, благодаря единой точке входа и говорящему именованию, подход открывает возможность к очень легкому и действенному логированию всего, что в данный момент происходит на UI-слое приложения.

Важную роль играет разделение ответственностей между классами. Если в каноничном MVP Presenter отвечал и за управление подписками, и за хранение, и за трансформацию данных, в данном подходе было решено разделить его на независимые части.

Модель View в каноничном виде MVI всегда отражает полное состояние экрана, и при любом изменении модели требуется полная перерисовка экрана. Этот подход реализован с помощью State Reducer Pattern. Однако из-за перерисовки экрана при получении каждого нового события, может значительно страдать производительность. Это решается при помощи Вычисления диффов при отрисовке состояния

Ответственности классов

В текущей реализации библиотеки сущности и их ответственности остались теми же, как в предыдущей версии, в которой использовалась RxJava.

  1. State Состояние экрана. Содержит всю необходимую информацию для того, чтобы отрисовать вью. Для хранения Observable состояния используется обертка StateHolder.
  2. Event Событие, происходящее на экране. Клик пользователя, получение данных из базы, запрос к серверу - все это описывается событиями. В Surf-MVI по умолчанию нет строгого разделения событий на события, источником которых был UI (например клик) или события, полученные в результате взаимодействия с сервисным слоем (например при получении ответа на запрос к серверу). Группировка событий остается на усмотрение разработчика с помощью sealed class.
  3. EventHub Шина для отправки и получения событий. Все события, которые эмитит UI или сервисный слой, оказываются в этой шине.
  4. Reducer Отвечает за изменение текущего состояния (и отправку сайд-эффектов, о них чуть позже). Каждый раз, когда какое-то событие оказывается в EventHub’е, оно прилетает в редьюсер, который должен решить, нужно ли как-то изменить состояние. Редьюсер, и только редьюсер, может менять состояние вью.
  5. Middleware Трансформирует поток событий из EventHub’а в поток новых событий. Например событие клика по кнопке в событие начала загрузки данных и после этого ошибки или успешного окончания загрузки.
  6. ScreenBinder Связывает все остальные сущности для совместного взаимодействия. События EventHub’а отправляются в Middleware для трансформации, в то же время Reducer получает уведомление о каждом событии, которое попадает в EventHub.

DSL

Одной из главных фишек библиотеки Surf-MVI был DSL, позволяющий описывать трансформации событий в лаконичном стиле используя средства котлина.
mvi-flow уже подерживает почти все DSL-трансформации, которые поддерживала предыдущая версия Подробнее про DSL можно почитать в DSL

Вычисление диффов при отрисовке состояния

Если мы используем Android View Framework и просто подпишемся на Observable состояние экрана и будем каждый раз полностью его перерисовывать, когда получим новое состояние, то на экранах с большим количеством логики и сложными вью может упасть FPS. И чем чаще будет обновляться состояние, тем сильнее будет падение. Для этого было сделано простое решение: перерисовывать только те части экрана, которые поменялись или отрисовываются в первый раз. На проектах для этого использовалась утилитарная функция actionIfChanged или одна из ее перегрузок для разного количества аргументов (обычно больше четырех для одной вью не трубуется, а для особых случаев можно сделать обертку).

fun <T : View, R1, R2, R3, R4> T.actionIfChanged(
    data1: R1?,
    data2: R2? = null,
    data3: R3? = null,
    data4: R4? = null,
    action: T.(data1: R1?, data2: R2?, data3: R3?, data4: R4?) -> Unit) {
    val hash = data1?.hashCode() ?: 0
        .plus(data2?.hashCode() ?: 0)
        .plus(data3?.hashCode() ?: 0)
        .plus(data4?.hashCode() ?: 0)
    if (this.tag != hash) {
        action(data1, data2, data3, data4)
        this.tag = hash
    }
}

При использовании Jetpack Compose дополнительные действия не потребуются, так как в Compose подсчет Diff есть из коробки и Compose отлично подходит под работу с единым состоянием.

Пример

В модуле sample можно посмотреть две реализации с полным набором сущностей.

  • Простая с примерами использования трансформаций и тривиальной логикой экрана
  • С запросом в сеть и обработкой ошибок с помощью ErrorHandler (используется на большинстве проектов).