![](https://habrastorage.org/getpro/habr/upload_files/c4b/cfe/098/c4bcfe09854ec7c786f2b5aaa5ba2c8c.jpeg)
Привет, меня зовут Елена Яновская, 5 лет в iOS-разработке, TechLead iOS в Альфа-Банке. Участвую в разработке главного экрана и активно продвигаю использование SDUI в фичах.
Контекст: для устранения проблем с виджетами, о которых Сергей рассказал в прошлой статье Как катить фичи без релизов. Часть 1: про виджеты, было решено разработать более низкоуровневый и гибкий подход для динамической отрисовки UI мобильного приложения с сервера.
Низкоуровневый Server Driven UI мы решили сделать максимально приближенным к дизайн-системе Альфа Мобайла, чтобы с его помощью отрисовать любой UI, который нам позволяет сделать дизайн-система. Например, если дизайнер указал, что цвет фона в данном компоненте кастомизируется, то SDUI модель для этого компонента должна, по умолчанию, уметь настраивать цвет с бэкэнда. Аналогично со всеми другими свойствами компонентов, например, со шрифтом или его размером.
Дизайн-система Альфа Мобайла имеет довольно обширную библиотеку UI-компонентов, она состоит из различных вью и врапперов для вью.
![](https://habrastorage.org/getpro/habr/upload_files/273/7c5/795/2737c579574022124566c673be29942f.png)
При этом у каждого из компонентов могут быть свои различные визуальные вариации, например, у кнопки ниже может быть разный цвет фона, наличие иконки и размер кнопки.
![Визуальные состояния на примере ButtonView. Визуальные состояния на примере ButtonView.](https://habrastorage.org/getpro/habr/upload_files/e63/c60/ac2/e63c60ac2d0a3087ecf48484fd8b847f.png)
Все эти компоненты и их вариации на iOS и на Android существуют уже давно. Чтобы реализовать ServerDrivenUI, нам оставалось научиться их динамично и гибко конфигурировать с бэкенда.
Сущности SDUI
Базовые енамы. В основе нашей дизайн-системы лежат такие минорные единицы, как цвет, типографика и спейсинги. Значения этих единиц строго зафиксированы и имеют свой нейминг. Эти значения мы в первую очередь поддержали в нашем новом SDUI.
![](https://habrastorage.org/getpro/habr/upload_files/a00/d7f/2ee/a00d7f2eeb11243a4944b4f0822cc1c6.png)
Цвета, типографику и спейсинги мы представили в виде енамов, значения которых совпадают со строками, которые нам может прислать сервер.
Атомы. Также у нас есть атомы — это базовые UI-элементы, из них построить интерфейс нельзя, но они входят в состав UI-компонентов. Например, это атомы Text и Icon.
![Атомы и их свойства Атомы и их свойства](https://habrastorage.org/getpro/habr/upload_files/ebe/63e/38b/ebe63e38bb64a9e9a95bd36f03e066b8.png)
У каждого атома есть свои свойства. Например, для атома Text мы можем задать типографику, цвет, которые у нас представлены в виде енамов, и, непосредственно, value — значение самого текста. У Icon тоже есть свойство цвета, а также url, либо name картинки, в зависимости от того, откуда мы хотим её загрузить.
Проще говоря, чтобы не дублировать свойства, мы их инкапуслировали в модель атома.
Компоненты. И, наконец, в SDUI есть модели для самих компонентов, которые могут состоять из других компонентов. Например, для описания DataView (ниже), нам нужно описать два её вложенных компонента DataContent и IconView.
DataContent состоит из трёх атомов текста.
IconView имеет атом Icon, а также какие-то дополнительные свойства, как Size и Shape.
![](https://habrastorage.org/getpro/habr/upload_files/0a0/1eb/74a/0a01eb74a33ad8ade27dd67733a4a578.png)
Таким образом, здесь мы можем увидеть конечную иерархию SDUI модели для DataView. Она имеет много вложенностей:
сама DataView состоит из двух компонентов;
каждый из компонентов уже может состоять из атомов;
а атомы могут иметь значения из енамов.
Такая иерархичность SDUI-модели каждого компонента дизайн-системы позволяет нам с самого начала заложить максимальную гибкость для UI.
Ответы с сервера в мобильном приложении Альфа-Банка приходят в виде JSON. В нём же теперь может прийти конфигурация для нашего UI, например, для DataView.
![](https://habrastorage.org/getpro/habr/upload_files/73a/928/0f3/73a9280f3e78c91266bba71a3fdce38e.png)
Для упрощения понимания этой системы среди разработчиков и аналитиков Альфа Мобайла, мы добавили previewer в отдельное iOS-приложение. Previewer — это механизм, чтобы прямо в рантайме подогнать дефолтный JSON под нужную нам view, меняя JSON и проверяя изменившееся отображение view в соседней вкладке.
Промежуточные итоги. Изначально у нас был фреймворк для UI-компонентов. В нём лежали сверстанные view и вью-модели для них, с помощью которых мы могли сконфигурировать эти view. Этот фреймворк остался неизменным.
Но дополнительно у нас появился фреймворк SDUI, в котором лежали енамы, атомы и DTO-модели для наших UI-компонентов. Там же появились мапперы: они брали на вход DTO-модель и возвращали сконфигурированную вью-модель.
![](https://habrastorage.org/getpro/habr/upload_files/abf/8fe/ba9/abf8feba99d3ab9d1799c17fceda4e32.png)
Теперь возникает вопрос — как нам расположить полученную view относительно других элементов на экране? Умеет ли наш фреймворк в Layout?
Layout SDUI элементов
У нас есть 2 инструмента: StackView и ConstraintWrapper.
![](https://habrastorage.org/getpro/habr/upload_files/4ab/10e/cff/4ab10ecffb9f5fead736787b3a2eaade.png)
StackView соответствует нативным UIStackView на iOS и LinearLayout на Android. Он позволяет нам построить элементы в линейном лэйатуе. На картинке показано, как в теории мы могли бы присылать с бэкенда view для обмена валют, представив её в виде стэка из вложенных вертикальных стэков, в каждом из которых по три текста.
Как и в нативном UIStackView, в нем есть поле alignment
(выравнивание элементов). На примере ниже оно установлено как fill
— заполняет все свободное пространство.
![](https://habrastorage.org/getpro/habr/upload_files/66b/a91/da1/66ba91da1a4acc7a13d16509ded9a2f5.png)
Если заменить значение на end
, то элементы сдвинутся к правому краю.
![](https://habrastorage.org/getpro/habr/upload_files/453/4a6/fd7/4534a6fd7abbe75569622cd5985a8de8.png)
ConstraintWrapper — это инструмент для построения лэйаутов на ограничениях интерфейса (констрейнтах), он соответствует AutoLayout на iOS и ConstraintLayout в Android. В нём есть модель с атрибутами ограничений, которые можно поделить на 2 типа: относительные и фиксированные.
Относительное ограничение — ограничение относительно какого-то элемента. Чтобы задать элемент, относительно которого нам нужен констрейнт, в поле reference
мы ставим тег этого элемента. В поле constant
мы указываем значение смещения по горизонтальной или вертикальной оси.
![](https://habrastorage.org/getpro/habr/upload_files/bc7/228/2d0/bc72282d079fb9690c6b398519bf4209.png)
Фиксированные ограничения нужны для ширины и высоты. Для них указывается лишь constant
, то есть само значение ширины или высоты элемента.
![](https://habrastorage.org/getpro/habr/upload_files/be1/9a4/396/be19a43968a4492ba84f1a4cad6b2db2.png)
Как же этот ConstraintWrapper описать JSON-ом? Представим, что нам надо расположить 8 элементов в виде ромбика.
![](https://habrastorage.org/getpro/habr/upload_files/1de/0b9/561/1de0b956162de77f3759a8923980a07d.png)
У каждого элемента есть свой набор констрейнтов. Рассмотрим на примере A и F, как это происходит в ConstraintWrapper.
Для А нам нужно, чтобы:
его верхний край соответствовал верхнему краю его
superview
со смещением 5;он располагался по оси Х относительно родителя;
и его ширина была равна 50.
![](https://habrastorage.org/getpro/habr/upload_files/d23/f9f/db6/d23f9fdb6cbab58cc62c066e3565f79d.png)
В массиве для ограничений этого элемента мы указываем в type
тип ограничения top
с константой 5, centerX
с нулевой константой, а также width
c константой 50. Для относительных констрейнтов в reference
указываем superview
— относительно родителя.
Для F нам нужно построить констрейнты относительно элемента А:
верхний край должен соответствовать нижнему краю первого элемента со смещением 5;
левый край должен соответствовать правому краю первого элемента со смещением 5.
У первого элемента «А» тэг был ButtonView1
, соответственно, в JSON-е у поля reference
будет значение ButtonView1
.
![](https://habrastorage.org/getpro/habr/upload_files/2ee/8d4/1e8/2ee8d41e84c59ede0324f0f4263b5030.png)
Аналогично строим массив констрейнтов для остальных элементов.
Отдельное внимание хочется уделить базовому элементу Server-Driven UI — LayoutElement. Это общая сущность, объединяющая все поддерживаемые в SDUI UI-компоненты. Она нужна для разных целей, где нельзя заранее определить тип присылаемого компонента, например, для StackView.
![](https://habrastorage.org/getpro/habr/upload_files/59a/053/c5c/59a053c5c13b11867f536274bea729f0.png)
Бонусом этот LayoutElement позволяет нам с сервера прислать экшен, который обработается, как только пользователь кликнет по элементу. Например, мы можем указать диплинк или разметку аналитики, которая будет отправлена после клика.
А что с документацией?
У нас появилось много сущностей, в которых легко запутаться — нужна документация. В качестве аннотации для всех SDUI моделей была выбрана JSON-схема. Это не полноценный JSON response, а скорее способ его описания.
![](https://habrastorage.org/getpro/habr/upload_files/b0e/ce7/773/b0ece77731ff404a2ad0a0e310b45f66.png)
JSON-схема состоит из базового описания сущности: name
для названия объекта, type
- это либо object
, либо enum
, и description
для описания сущности на русском языке.
![](https://habrastorage.org/getpro/habr/upload_files/d6f/0eb/604/d6f0eb60471db5e1b7bf8156146dc0d3.png)
Также в него входят характеристики каждого поля, например, указание обязательности поля и снова description
.
![](https://habrastorage.org/getpro/habr/upload_files/9a3/288/64c/9a328864c8f38968e04c4475b99e5b9f.png)
Эти JSON схемы также позволили нам добавить на фронт и бэкенд кодогенерацию моделей. JSON схемы лежат в отдельном репозитории.
![](https://habrastorage.org/getpro/habr/upload_files/9ab/d1c/0de/9abd1c0dea7df719aae74884ba23d522.png)
Сам процесс добавления нового компонента в SDUI состоит из трёх шагов:
Сначала разработчик составляет JSON-схему для компонента, согласует её с дизайнером.
Затем он проходит код ревью своей схемы, в ходе которого ему нужно получить по 1 аппруву от iOS, Android и Backend-разработчика.
И уже после этого он пишет реализацию для своей платформы.
Такой процесс позволяет нам всегда сохранять документацию в виде JSON-схем актуальной, и использовать этот репозиторий как единую точку правды.
Как это работает вместе с виджетами?
Виджеты и SDUI — это две разных системы и два разных фреймворка.
Виджеты могут отвечать и за логику, и за UI, а SDUI только за UI. Как же нам сделать виджет с SDUI?
Для этого у нас разработаны виджеты SDUI Widget, VerticalListWidget для вертикального списка элементов и HorizontalListWidget для горизонтального списка элементов.
Связка с SDUI происходит за счёт того, что его поле content
маппится к нашему универсальному LayoutElement (о нём я рассказывала выше), который может быть как единичным элементом, так и стеком из других элементов. Благодаря этому, мы можем прислать абсолютно любой UI в наш виджет.
![](https://habrastorage.org/getpro/habr/upload_files/5c7/73f/2e7/5c773f2e704e66dc623a0e4db8bada78.png)
На картинке выше представлен пример для JSON response такого SDUI виджета, поля из его верхней части обрабатываются самим виджетов, а содержимое в поле content
обрабатывает SDUI фреймоворк. Он принимает на вход эту структуру и возвращает уже полностью сконфигурированную view, готовую для её отображения в виджете.
Таким образом, SDUI Widget позволяет изначально предусмотреть все возможные конфигурации его вложенных view, тем самым сокращая количество копипасты и доработок на фронте, решая большую часть проблем виджетов.
Что в итоге?
Виджеты — это фреймворк для динамического управления отображением фичей на экране-хабе.
Server Driven UI — это язык на базе JSON, позволяющий сконфигурировать и отрисовать View.
В зависимости от фичи, мы можем использовать разный уровень абстракции.
Меньше копипасты в реализации UI виджета.
Можно сделать разную верстку при Zero coding.
![](https://habrastorage.org/getpro/habr/upload_files/f7d/c7b/f28/f7dc7bf2869d6de91ff714b6791c333d.png)
К сожалению, 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
Спасибо :)