Эволюция Server-Driven UI: динамические поля, хэндлеры и многошаг

Server-Driven UI (SDUI) — это подход для динамичного и гибкого пользовательского интерфейса, когда сервер посредством API сообщает приложению, какие компоненты и с каким контентом отображать. Он довольно популярен, и мы его тоже используем на многих экранах — помогает быстро выпускать фичи в продакшн. 

В статье покажу, на каких экранах мы его применяем, и расскажу, как развивались у нас подходы гибкого UI, какие плюсы и минусы мы вывели из его использования. Сначала рассмотрим формы на динамических полях, контракты и ​тонкие моменты создания новых полей. Потом поговорим про динамические флоу, как за ноль калорий на стороне фронта добавлять новых провайдеров для проведения оплаты и закончим зависимыми полями. Всё это с примерами экранов мобильного приложения. 

c67a47df1a98f45eafaaf1e94bbea681.jpg

Об авторе:

5e2e90b2cd07bed94432ced6401633ce.jpgАнна Саботович

В Android-разработке с 2010 года, техлид в Альфа-Банке

Динамические поля

Динамические поля у нас появились, потому что мы хотели иметь возможность настраивать состав экрана с бэкенда: когда форма меняется в зависимости от профиля пользователя и открытых на него фича-тогглов (feature toggle).

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

e0ced50b4cc1607ea41fd50ccd90b3fb.png

Они работают так:

  • от сервера приходит массив моделей с разными типами и набором параметров;

  • мы парсим этот массив и маппим на компоненты дизайн-системы;

  • в итоге получается экран, сверстанный на компонентах, состав которого легко и быстро меняется с бэка. 

Список динамо-полей можно сочетать со статической вёрсткой и вставлять его в любое место экрана.

Контракт динамических полей

Покажу пример ответа с динамическими полями.

b536db48efab4ebd7177aedc5640dc7a.png

В ответе от бэка на любой из ендпоинтов может прийти массив моделей динамических полей. Мы их различаем по полю type и маппим в разные модели, в зависимости от типа. 

Кроме типа у динамо-поля должны быть:

Разные типы динамо полей маппятся в разные компоненты дизайн-системы, от простых, типа строкового заголовка, до сложных, например, поле выбора счёта.

Поля делятся на две категории:  

  • output поля, которые просто выводят данные, например HEADER или STRING;  

  • input поля — интерактивные: пользователь может поменять, например, INPUT, CHECKBOX, ACCOUNT_SELECT.

93d2e5c68dd8a3ca4129552e247e8570.png

У интерактивного поля может быть булев параметр required. Если он true, то форма не будет отправлена на бэк, пока это поле не будет заполнено. Также у интерактивных полей может присутствовать модель, которая описывает правила валидации, например минимальная или максимальная длина строки, и любые регулярки.

Примечание. Также важна обратная совместимость. Мы решили, что если фронт по какой-либо причине не может распарсить модель от сервера, то мы не отображаем поле.

Новые поля

Продакты любят динамо-поля, поэтому всё время появляются запросы на новые типы. И тут возникает две проблемы:

Первая проблема: часто при получении дизайна нового поля думаем — заводить под это поле новый тип или лучше расширить какое-то из существующих?

Здесь мы стараемся соблюсти баланс между универсальностью поля и сложностью его контракта.

  • Если новое поле отличается минимально, то просто добавляем в модель новые параметры.

  • Если оно кардинально новое, или мы видим, как сделать его универсальным сразу, то заводим новый тип и придумываем контракт для него. 

На картинке показали примеры полей с разными типами, хоть они во многом и похожи. Последнее поле — самое новое. Оно в итоге объединит первые два.

ccb0c80ca69964b624558468a22391f8.png

Вторая проблема: дизайнеры пытаются добавить в динамо-поля слайдеры с картами и невероятной анимацией, которые будут использоваться только на одном экране. На картинке как раз пример такого динамо-поля — это кастомный прогресс бар для одной единственной фичи.

9f64304ad5aa801933e41158ce1b8dd2.png

Как раз динамо-поля помогли нам решить задачу быстрой и гибкой настройки контента экрана. 

Когда у бизнеса появился запрос настраивать не только состав одного экрана, но и вообще порядок следования экранов друг за другом и зависимость контента следующего экрана от результатов пользовательского ввода на предыдущем, у нас появился многошаг.

Многошаг

Это апишка, которая на вход принимает данные с текущей формы, а на выход отдает набор динамо-полей для следующего экрана. 

Количество шагов многошага формально бесконечно, его конфигурация определяется на бэке. На картинке показали пример возможного многошага.

96680a542633af914d4cb8018ea00b5a.png

Этот многошаг состоит из трёх шагов:

Шаг №1. Пользователю приходят динамо-поля для ввода имени, даты рождения, электронной почты и дисклеймер. Также в респонсе есть модель для настройки кнопки внизу экрана. 

  • При нажатии на неё сначала происходит валидация формы на фронте по правилам, которые зашиты в каждое динамо-поле.

  • Затем формируется массив с id полей и введенными в них данными. 

  • Этот массив отправляется в API многошага, а он отдаёт новый массив динамо-полей. Например, на втором шаге могут прийти поля для ввода паспорта, если человек старше 14 лет, или для ввода свидетельства о рождении, если младше 14.

Шаг №2. От сервера приходят данные для ввода паспорта. После ввода данных, пользователь нажимает кнопку «Продолжить» и всё повторяется: валидация, сбор значений полей в массив, отправка на бэк, получение данных для нового экрана.

Многошаг будет продолжаться пока в ответе не придёт признак, что текущий шаг последний.

Шаг №3. Последний шаг может быть просто терминальным экраном, например финальным, с информацией, что всё заполнено. А может быть экраном, ведущим на флоу подтверждения оплаты.

Вырожденный вид многошага — одношаг: вторым шагом он всегда переходит на финальный экран.

Многошаг — это уже более строгая апишка. По сути, это одна активити с ресайклером, так что рисовать на ней мы можем только динамо-поля, никакой дополнительной статической верстки быть не может.

Все платежи в пользу сторонних провайдеров в Альфа-Мобайл у нас реализованы на многошаге. При выборе провайдера, например, ЖКУ Москвы или оплаты штрафов, вызывается диплинк, который ведёт на базовый экран многошага и дергает API с Query-параметрами из диплинка.

Этот механизм даёт нам возможность за ноль калорий на стороне фронта добавлять новых провайдеров для проведения оплаты, изменять в них количество шагов, делать фиксы. Кроме того, любые флоу, которые укладываются в концепцию многошага, могут быть быстро развернуты на всех платформах с использованием уже имеющихся динамо полей. Нужно только сделать точку входа на фронте, а вся остальная логика уже будет реализована на бэке. 

Многошаг отлично дополнил возможности динамо-полей, он позволил нам делать гибкие последовательности экранов. С расширением внедрения многошага и динамо-полей стали появляться кейсы изменения состояния экрана без обновления страницы или перехода на следующий шаг, например, чтобы показать или скрыть какие-то поля на форме в зависимости от выбора чекбокса. 

Для решения этой задачи мы сделали поля зависимыми друг от друга.

Зависимые поля

Перед вами пример, который стал пилотом для реализации зависимых динамо-полей. Здесь стояла задача гибко настраивать подписку, например на штрафы. Если пользователь выбрал свитчер «Оплачивать счета автоматически», то мы показываем ему поле с выбором счета списания и возможностью установить лимит платежа. 

808eafb36f27522e9c1a3ce202053b7f.png

Если пользователь выбрал установить лимит, то показываем ему поле ввода лимита.

Чтобы это реализовать, мы сделали надстройку над массивом динамо полей, в которой записаны правила зависимости полей друг от друга и их поведение. Эту надстройку назвали хэндлеры. Они приходят при загрузке экрана вместе со списком полей и обновление экрана происходит локально, без дополнительных запросов на бэк.

Схема работы зависимых полей

Выглядит так.

55d06ccc7a421ff290682a1f7c0e612b.png

Сейчас расскажу подробнее, как это работает. 

  • От бэка приходит массив полей и хендлеров, экран отрисовывается. 

  • Пользователь взаимодействует с каким-либо полем, например меняет свитчер. Выбрасывается событие с id измененного поля и типом произошедшего события. 

  • Это событие поступает на вход в библиотеку хэндлеров. Ищется подходящий этому событию триггер для хэндлера. Если он найден, значит у этого поля есть зависимые поля. 

  • Дальше проверяются условия срабатывания хэндлера, например, свитчер включен или выключен.

  • Если условие выполнено, то выполняются операции заложенные в хэндлер, например скрыть определенные поля. Если никаких условий у хэндлера нет, то операция выполняется сразу.

Примечание. Каскадные выбрасывания событий у нас пока не реализованы, но в контракт такая возможность заложена.

Рассмотрим подробно каждый элемент хэндлера.

Триггер

Определяют, для каких событий должен срабатывать данный хэндлер.

Триггеры содержат параметры:

  • id поля, по которому определяется, от какого поля должно прийти событие на обновление;

  • тип события, по которому определяется, по какому типу события должен срабатывать хэндлер, например, SELECT, CHANGE или CLEAR.

Когда какой тип триггера срабатывает:

  • SELECT — когда выбрано значение из выпадающего списка;

  • INIT — сразу же после получения массива динамо полей;

  • CLEAR — при очищении поля;

  • CHANGE — при изменении значения в поле.

Условия

Условия — это логические выражения, которые проверяют и сравнивают данные с формы. Если они истинны, то хэндлер выполнится.

Условия могут быть составными, и количество уровней вложенности не ограничено. Они могут сравнивать, как со статическим данными, так и с данными, взятыми из любого поля или из контекста выполнения экрана. При этом условия необязательны в хэндлере: если их нет, то хэндлер выполнится в любом случае при срабатывании триггера.

Некоторые из реализованных условий:

  • EQUALS — проверка на равенство;

  • CONTAINS — равно одному из значений из списка;

  • NOT — отрицание;

  • AND / OR / LESS — и/или/меньше.

Операции

Операции описывают список действий, которые должен сделать хэндлер.

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

Некоторые из реализованных операций:

  • HIDE_FIELDS / SHOW_FIELDS — скрыть или показать поля из списка;

  • SET_VALUE — установить значение в поле;

  • SET_REQUIRED — сделать поле обязательным (или нет).

Итоги

Динамические поля, хэндлеры и многошаг вместе — это мощный инструмент, который покрывает большинство наших кейсов, когда нужен динамический UI.

К плюсам я бы отнесла:

  • Быструю реализацию новых фич на уже существующих компонентах.

  • Быстрый багфикс и поставка изменений клиенту.

Минусы тоже присутствуют:

  • Большой оверхед при разработке на старте. Приходится долго согласовывать контракты между всеми платформами и стараться всё время думать об универсальности и расширяемости.

  • Много логики и на бэке и на фронте, это даёт высокий порог вхождения для новых разработчиков.

  • Дизайнеры часто упираются в ограниченность разработанных компонентов и хэндлеров, что влечёт постоянный выбор: «ехать» на реализованных компонентах и упростить дизайн, расширять компонент или вообще делать новый.

Вы узнали о некоторых шагах развития SDUI, которые наша команда прошла за время жизни проекта. Но каждый день пользователи и бизнес ставит перед нами новые задачи, и есть уверенность, что наши подходы будут развиваться дальше и помогут решать сложные инженерные задачи. Надеюсь, наработки будут полезны и вам.

Этот пост сделан на основе недавнего доклада Анны Саботович на конференции Mobius. Если захотелось посмотреть этот доклад целиком и вообще приобщиться к конференции — 22 июня у Mobius пройдёт офлайн-день в Санкт-Петербурге. Если приобрести билет на него, то заодно получите видеозапись доклада Анны и многих других. А если хотите больше узнать о работе в Альфа-Банке — обратите внимание на Telegram-канал Alfa Digital Jobs. Там мы интересно и весело рассказываем про нашу работу, делимся новостями и полезными советами, иногда даже шутим.

Рекомендуем почитать другие статьи:

© Habrahabr.ru