Привет, меня зовут Елена Яновская, 5 лет в iOS-разработке, TechLead iOS в Альфа-Банке. Участвую в разработке главного экрана и активно продвигаю использование SDUI в фичах.

Контекст: для устранения проблем с виджетами, о которых Сергей рассказал в прошлой статье Как катить фичи без релизов. Часть 1: про виджеты, было решено разработать более низкоуровневый и гибкий подход для динамической отрисовки UI мобильного приложения с сервера. 

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

Дизайн-система Альфа Мобайла имеет довольно обширную библиотеку UI-компонентов, она состоит из различных вью и врапперов для вью.

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

Визуальные состояния на примере ButtonView.
Визуальные состояния на примере ButtonView.

Все эти компоненты и их вариации на 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 - это независимый фреймворк, его можно использовать и на обычных фичевых экранах.

Используете ли вы подобные виджеты у себя? А может у вас есть, что добавить по теме? Напишите в комментариях, будет интересно.


Рекомендованные статьи:

Также подписывайтесь на Телеграм-канал Alfa Digital — там мы постим новости, опросы, видео с митапов, краткие выжимки из статей, иногда шутим.

Комментарии (13)


  1. varton86
    26.06.2023 11:38

    Спасибо, интересный подход, хотя кажется избыточным.

    А каким образом происходит наполнение json на бэке для получения в mobile app (важна ведь последовательность элементов, положение относительно друг друга и т.д.), есть некий фронт для проверки получившегося в результате UI?

    p.s. "Если заменить значение на end, то элементы сдвинутся к правому краю" видимо нужно поправить картинку с alignment.


    1. yanovskaya Автор
      26.06.2023 11:38
      +1

      Даже такая, казалось бы, избыточная функциональность часто требует расширения, чтобы выпускать фичи без релиза :)
      Сейчас JSON составляет системный аналитик, опираясь на JSON-схемы и документацию по каждому элементу. Для проверки он использует preview-приложения на iOS и Android, где в рантайме можно ввести JSON и увидеть его отображение (либо ошибку с описанием, какой элемент не парсится). Также у нас в разработке механизм генерации этих JSON-ов по фигме с макетами.

      Иллюстрация к alignment = end под текстом, там элементы как раз сдвинуты к правому краю :)


      1. varton86
        26.06.2023 11:38

        Спасибо, а после того, как аналитик разработал json, что он делает - отдает json бэкендеру или сам загружает на бэк? Как json попадает на бэк, вручную кладут или есть инструмент для этого? Кроме того, layout подразумевает иерархию объектов, как она выглядит в итоге, один большой json, со всеми зависимостями?

        p.s.
        Про alignment я имею ввиду, что у вас картинка с json одинаковая: "algnment" : "fill" для обоих примеров, а должна быть "alignment" : "end" во втором.


        1. akimserg
          26.06.2023 11:38

          Тоже интересен этот момент, так как по опыту использования data driven ui, самое сложное место это стык бекенда и вьюшек на приложении. Чтобы написать вьюшки нужно знать нуьюнасы приложения и как этот json парсится приложением, с другой стороны чтобы бекенд отдвал этот json его нужно как то разместить на бекенде, где в зависимости от логики приложения будет генерится этот json


          1. yanovskaya Автор
            26.06.2023 11:38

            Все нюансы мы стараемся сразу описать в документации к каждой сущности SDUI, там же храним примеры JSON-ов для типовых версток. Если вдруг аналитик столкнулся с некорректной верстой и дока ему не помогла, то он уже идет за помощью к мейнтейнерам Server-Driven UI (у нас их по 2 человека на каждую платформу)


        1. yanovskaya Автор
          26.06.2023 11:38

          Сейчас JSON передается на бэк, а бэкендер вручную собирает модель через конфигурацию. Планируем скоро зарелизить админку, где достаточно будет залить готовый JSON, и по имени фичи будет собираться объект, учитывая версии клиента/элементов.

          Насчет layout, да, это один большой JSON со всеми зависимостями. Составлять такой JSON может быть больно, но зато time to market сокращается значительно :)


          1. varton86
            26.06.2023 11:38

            Спасибо :)


  1. 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 (генерация данных и обработка клиенских запросов к базе)

    Есть проблемы, но в целом приложения получаются надежными (ссылочная целостность не дает ломаться генерации клиентской и серверной части).


    1. yanovskaya Автор
      26.06.2023 11:38
      +1

      Привет! Действительно, интересный поход! Хотелось бы о нем узнать подробнее, как реализовывали и с какими проблемами столкнулись.


      1. vagon333
        26.06.2023 11:38
        -1

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


  1. funca
    26.06.2023 11:38
    +3

    Видимо data-driven UI это то, что нельзя просто так взять и внедрить со старта - компания до него может только дорасти. Можно использовать как маркер уровня технической зрелости. Сам же подход стар как мир: TCL/TK, UIML, XAML, XUL, BEMJSON, QML..


    1. yanovskaya Автор
      26.06.2023 11:38
      +1

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


  1. Andreu_s
    26.06.2023 11:38
    +1

    Супер! Спасибо за хорошо изложенную информацию!)