Привет, меня зовут Елена Яновская, 5 лет в iOS-разработке, TechLead iOS в Альфа-Банке. Участвую в разработке главного экрана и активно продвигаю использование SDUI в фичах.
Контекст: для устранения проблем с виджетами, о которых Сергей рассказал в прошлой статье Как катить фичи без релизов. Часть 1: про виджеты, было решено разработать более низкоуровневый и гибкий подход для динамической отрисовки UI мобильного приложения с сервера.
Низкоуровневый Server Driven UI мы решили сделать максимально приближенным к дизайн-системе Альфа Мобайла, чтобы с его помощью отрисовать любой UI, который нам позволяет сделать дизайн-система. Например, если дизайнер указал, что цвет фона в данном компоненте кастомизируется, то SDUI модель для этого компонента должна, по умолчанию, уметь настраивать цвет с бэкэнда. Аналогично со всеми другими свойствами компонентов, например, со шрифтом или его размером.
Дизайн-система Альфа Мобайла имеет довольно обширную библиотеку UI-компонентов, она состоит из различных вью и врапперов для вью.
При этом у каждого из компонентов могут быть свои различные визуальные вариации, например, у кнопки ниже может быть разный цвет фона, наличие иконки и размер кнопки.
Все эти компоненты и их вариации на iOS и на Android существуют уже давно. Чтобы реализовать ServerDrivenUI, нам оставалось научиться их динамично и гибко конфигурировать с бэкенда.
Сущности SDUI
Базовые енамы. В основе нашей дизайн-системы лежат такие минорные единицы, как цвет, типографика и спейсинги. Значения этих единиц строго зафиксированы и имеют свой нейминг. Эти значения мы в первую очередь поддержали в нашем новом SDUI.
Цвета, типографику и спейсинги мы представили в виде енамов, значения которых совпадают со строками, которые нам может прислать сервер.
Атомы. Также у нас есть атомы — это базовые UI-элементы, из них построить интерфейс нельзя, но они входят в состав UI-компонентов. Например, это атомы Text и Icon.
У каждого атома есть свои свойства. Например, для атома Text мы можем задать типографику, цвет, которые у нас представлены в виде енамов, и, непосредственно, value — значение самого текста. У Icon тоже есть свойство цвета, а также url, либо name картинки, в зависимости от того, откуда мы хотим её загрузить.
Проще говоря, чтобы не дублировать свойства, мы их инкапуслировали в модель атома.
Компоненты. И, наконец, в SDUI есть модели для самих компонентов, которые могут состоять из других компонентов. Например, для описания DataView (ниже), нам нужно описать два её вложенных компонента DataContent и IconView.
DataContent состоит из трёх атомов текста.
IconView имеет атом Icon, а также какие-то дополнительные свойства, как Size и Shape.
Таким образом, здесь мы можем увидеть конечную иерархию SDUI модели для DataView. Она имеет много вложенностей:
сама DataView состоит из двух компонентов;
каждый из компонентов уже может состоять из атомов;
а атомы могут иметь значения из енамов.
Такая иерархичность SDUI-модели каждого компонента дизайн-системы позволяет нам с самого начала заложить максимальную гибкость для UI.
Ответы с сервера в мобильном приложении Альфа-Банка приходят в виде JSON. В нём же теперь может прийти конфигурация для нашего UI, например, для DataView.
Для упрощения понимания этой системы среди разработчиков и аналитиков Альфа Мобайла, мы добавили previewer в отдельное iOS-приложение. Previewer — это механизм, чтобы прямо в рантайме подогнать дефолтный JSON под нужную нам view, меняя JSON и проверяя изменившееся отображение view в соседней вкладке.
Промежуточные итоги. Изначально у нас был фреймворк для UI-компонентов. В нём лежали сверстанные view и вью-модели для них, с помощью которых мы могли сконфигурировать эти view. Этот фреймворк остался неизменным.
Но дополнительно у нас появился фреймворк SDUI, в котором лежали енамы, атомы и DTO-модели для наших UI-компонентов. Там же появились мапперы: они брали на вход DTO-модель и возвращали сконфигурированную вью-модель.
Теперь возникает вопрос — как нам расположить полученную view относительно других элементов на экране? Умеет ли наш фреймворк в Layout?
Layout SDUI элементов
У нас есть 2 инструмента: StackView и ConstraintWrapper.
StackView соответствует нативным UIStackView на iOS и LinearLayout на Android. Он позволяет нам построить элементы в линейном лэйатуе. На картинке показано, как в теории мы могли бы присылать с бэкенда view для обмена валют, представив её в виде стэка из вложенных вертикальных стэков, в каждом из которых по три текста.
Как и в нативном UIStackView, в нем есть поле alignment
(выравнивание элементов). На примере ниже оно установлено как fill
— заполняет все свободное пространство.
Если заменить значение на end
, то элементы сдвинутся к правому краю.
ConstraintWrapper — это инструмент для построения лэйаутов на ограничениях интерфейса (констрейнтах), он соответствует AutoLayout на iOS и ConstraintLayout в Android. В нём есть модель с атрибутами ограничений, которые можно поделить на 2 типа: относительные и фиксированные.
Относительное ограничение — ограничение относительно какого-то элемента. Чтобы задать элемент, относительно которого нам нужен констрейнт, в поле reference
мы ставим тег этого элемента. В поле constant
мы указываем значение смещения по горизонтальной или вертикальной оси.
Фиксированные ограничения нужны для ширины и высоты. Для них указывается лишь constant
, то есть само значение ширины или высоты элемента.
Как же этот ConstraintWrapper описать JSON-ом? Представим, что нам надо расположить 8 элементов в виде ромбика.
У каждого элемента есть свой набор констрейнтов. Рассмотрим на примере A и F, как это происходит в ConstraintWrapper.
Для А нам нужно, чтобы:
его верхний край соответствовал верхнему краю его
superview
со смещением 5;он располагался по оси Х относительно родителя;
и его ширина была равна 50.
В массиве для ограничений этого элемента мы указываем в type
тип ограничения top
с константой 5, centerX
с нулевой константой, а также width
c константой 50. Для относительных констрейнтов в reference
указываем superview
— относительно родителя.
Для F нам нужно построить констрейнты относительно элемента А:
верхний край должен соответствовать нижнему краю первого элемента со смещением 5;
левый край должен соответствовать правому краю первого элемента со смещением 5.
У первого элемента «А» тэг был ButtonView1
, соответственно, в JSON-е у поля reference
будет значение ButtonView1
.
Аналогично строим массив констрейнтов для остальных элементов.
Отдельное внимание хочется уделить базовому элементу Server-Driven UI — LayoutElement. Это общая сущность, объединяющая все поддерживаемые в SDUI UI-компоненты. Она нужна для разных целей, где нельзя заранее определить тип присылаемого компонента, например, для StackView.
Бонусом этот LayoutElement позволяет нам с сервера прислать экшен, который обработается, как только пользователь кликнет по элементу. Например, мы можем указать диплинк или разметку аналитики, которая будет отправлена после клика.
А что с документацией?
У нас появилось много сущностей, в которых легко запутаться — нужна документация. В качестве аннотации для всех SDUI моделей была выбрана JSON-схема. Это не полноценный JSON response, а скорее способ его описания.
JSON-схема состоит из базового описания сущности: name
для названия объекта, type
- это либо object
, либо enum
, и description
для описания сущности на русском языке.
Также в него входят характеристики каждого поля, например, указание обязательности поля и снова description
.
Эти JSON схемы также позволили нам добавить на фронт и бэкенд кодогенерацию моделей. JSON схемы лежат в отдельном репозитории.
Сам процесс добавления нового компонента в SDUI состоит из трёх шагов:
Сначала разработчик составляет JSON-схему для компонента, согласует её с дизайнером.
Затем он проходит код ревью своей схемы, в ходе которого ему нужно получить по 1 аппруву от iOS, Android и Backend-разработчика.
И уже после этого он пишет реализацию для своей платформы.
Такой процесс позволяет нам всегда сохранять документацию в виде JSON-схем актуальной, и использовать этот репозиторий как единую точку правды.
Как это работает вместе с виджетами?
Виджеты и SDUI — это две разных системы и два разных фреймворка.
Виджеты могут отвечать и за логику, и за UI, а SDUI только за UI. Как же нам сделать виджет с SDUI?
Для этого у нас разработаны виджеты SDUI Widget, VerticalListWidget для вертикального списка элементов и HorizontalListWidget для горизонтального списка элементов.
Связка с SDUI происходит за счёт того, что его поле content
маппится к нашему универсальному LayoutElement (о нём я рассказывала выше), который может быть как единичным элементом, так и стеком из других элементов. Благодаря этому, мы можем прислать абсолютно любой UI в наш виджет.
На картинке выше представлен пример для JSON response такого SDUI виджета, поля из его верхней части обрабатываются самим виджетов, а содержимое в поле content
обрабатывает SDUI фреймоворк. Он принимает на вход эту структуру и возвращает уже полностью сконфигурированную view, готовую для её отображения в виджете.
Таким образом, SDUI Widget позволяет изначально предусмотреть все возможные конфигурации его вложенных view, тем самым сокращая количество копипасты и доработок на фронте, решая большую часть проблем виджетов.
Что в итоге?
Виджеты — это фреймворк для динамического управления отображением фичей на экране-хабе.
Server Driven UI — это язык на базе JSON, позволяющий сконфигурировать и отрисовать View.
В зависимости от фичи, мы можем использовать разный уровень абстракции.
Меньше копипасты в реализации UI виджета.
Можно сделать разную верстку при Zero coding.
К сожалению, SDUIWidget не везде применим. Для фичей с нестандартным UI, скорее всего, придется создавать новый виджет. Если же нам нужна простая верстка из набора UI-компонентов нашей дизайн-системы, то тут идеально подойдет SDUIWidget.
И, что важно, так как ServerDrivenUI - это независимый фреймворк, его можно использовать и на обычных фичевых экранах.
Используете ли вы подобные виджеты у себя? А может у вас есть, что добавить по теме? Напишите в комментариях, будет интересно.
Рекомендованные статьи:
Эволюция Server-Driven UI: динамические поля, хэндлеры и многошаг
Хочу в iOS-разработку: к чему готовиться на собеседовании в продуктовую команду
Также подписывайтесь на Телеграм-канал Alfa Digital — там мы постим новости, опросы, видео с митапов, краткие выжимки из статей, иногда шутим.
Комментарии (13)
vagon333
26.06.2023 11:38Приветствую коллег по подходу.
Использую data-driven UI в приложениях последние 6 лет.
Рисую UI на базе расширямых компонентов, пропертями, описанными в базе и доступными через UI.
Получается: мобильные, десктопные (web-app), browser extension.
Небольшие дополениня, как то:
- data-driven databases definition
- data-driven DB Tables definition
- data-driven server-side logic (генерация данных и обработка клиенских запросов к базе)
Есть проблемы, но в целом приложения получаются надежными (ссылочная целостность не дает ломаться генерации клиентской и серверной части).yanovskaya Автор
26.06.2023 11:38+1Привет! Действительно, интересный поход! Хотелось бы о нем узнать подробнее, как реализовывали и с какими проблемами столкнулись.
vagon333
26.06.2023 11:38-1Публичный коммент мало кому интересен. Пишите в личку.
Буду рад сделать демку, обсудим взаимные сложности и варианты решений.
funca
26.06.2023 11:38+3Видимо data-driven UI это то, что нельзя просто так взять и внедрить со старта - компания до него может только дорасти. Можно использовать как маркер уровня технической зрелости. Сам же подход стар как мир: TCL/TK, UIML, XAML, XUL, BEMJSON, QML..
yanovskaya Автор
26.06.2023 11:38+1Да, есть такая тенденция :) Подходов к разметке UI действительно много, но многие разработчики создают свой, который бы выполнял конкретно их запросы и был удобен в разработке и сопровождении.
varton86
Спасибо, интересный подход, хотя кажется избыточным.
А каким образом происходит наполнение json на бэке для получения в mobile app (важна ведь последовательность элементов, положение относительно друг друга и т.д.), есть некий фронт для проверки получившегося в результате UI?
p.s. "Если заменить значение на end, то элементы сдвинутся к правому краю" видимо нужно поправить картинку с alignment.
yanovskaya Автор
Даже такая, казалось бы, избыточная функциональность часто требует расширения, чтобы выпускать фичи без релиза :)
Сейчас JSON составляет системный аналитик, опираясь на JSON-схемы и документацию по каждому элементу. Для проверки он использует preview-приложения на iOS и Android, где в рантайме можно ввести JSON и увидеть его отображение (либо ошибку с описанием, какой элемент не парсится). Также у нас в разработке механизм генерации этих JSON-ов по фигме с макетами.
Иллюстрация к
alignment
=end
под текстом, там элементы как раз сдвинуты к правому краю :)varton86
Спасибо, а после того, как аналитик разработал json, что он делает - отдает json бэкендеру или сам загружает на бэк? Как json попадает на бэк, вручную кладут или есть инструмент для этого? Кроме того, layout подразумевает иерархию объектов, как она выглядит в итоге, один большой json, со всеми зависимостями?
p.s.
Про alignment я имею ввиду, что у вас картинка с json одинаковая: "algnment" : "fill" для обоих примеров, а должна быть "alignment" : "end" во втором.
akimserg
Тоже интересен этот момент, так как по опыту использования data driven ui, самое сложное место это стык бекенда и вьюшек на приложении. Чтобы написать вьюшки нужно знать нуьюнасы приложения и как этот json парсится приложением, с другой стороны чтобы бекенд отдвал этот json его нужно как то разместить на бекенде, где в зависимости от логики приложения будет генерится этот json
yanovskaya Автор
Все нюансы мы стараемся сразу описать в документации к каждой сущности SDUI, там же храним примеры JSON-ов для типовых версток. Если вдруг аналитик столкнулся с некорректной верстой и дока ему не помогла, то он уже идет за помощью к мейнтейнерам Server-Driven UI (у нас их по 2 человека на каждую платформу)
yanovskaya Автор
Сейчас JSON передается на бэк, а бэкендер вручную собирает модель через конфигурацию. Планируем скоро зарелизить админку, где достаточно будет залить готовый JSON, и по имени фичи будет собираться объект, учитывая версии клиента/элементов.
Насчет layout, да, это один большой JSON со всеми зависимостями. Составлять такой JSON может быть больно, но зато time to market сокращается значительно :)
varton86
Спасибо :)