Итак, здравствуйте! Меня зовут Никита Синявин, я разработчик мобильных приложений в компании BetBoom. Сейчас наша команда использует Flutter для разработки некоторых своих приложений, на который мы в свою очередь успешно переехали с React Native. В этой статье я хотел бы представить обзор нового велосипеда фреймворка, который обеспечивает работу паттерна server driven UI для Flutter.
Начнем с небольшой вводной о предмете разговора. Server-Driven UI (или Backend-Driven UI) — это концепция в разработке приложений с интерфейсом, при котором бэкенд управляет как данными приложения, так и его внешним видом. Конкретно мое внимание этот паттерн привлек благодаря тому, что реализуя его, можно добавить в свое приложение интерактивные элементы интерфейса и при этом не создавать себе трудностей с публикацией новой версии приложения в сторы и уговоров пользователей обновиться. Часто для реализации подобной задумки (и в целях экономии) используют WebView, но всякий кто хоть раз работал с этой технологией, ни за что не вернется к ее использованию, при условии, что выполнить задачу можно иным путем.
Для Flutter уже существуют пакеты, которые так или иначе реализуют концепцию. Например server_driven_ui или sdui. Но у них есть ряд существенных недостатков:
Отсутствие поддержки. Последние обновления более полугода назад.
Отсутствие толковой документации. (server_driven_ui)
Большое количество сторонних зависимостей. (sdui)
Отсутствие "билдера" для json-файла по которым будет рендерится UI. Фактически - возрастает вероятность ошибки/опечатки на этапе создания json`a на стороне сервера.
Отсутствие механизмов связи с бекендом, который "раздал" нам ui. Ui не может получить реалтайм обновление со стороны сервера или вызвать какой-либо эндпоинт.
Глядя на эти недостатки обоих пакетов было принято решение написать свой собственный фреймворк, который решал бы описанные проблемы. Также я вдохновился некоторыми концепциями и творчески переосмыслил их в своей реализации (регистрация кастомных виджетов).
Duit - drived UI toolkit. Понятно почему тут присутствуют слова "drived" и "UI", а "toolkit" здесь по той причине, что Duit - это целый фреймворк, состоящий из двух основных частей: библиотеки для Flutter, которая обеспечивает отрисовку UI, а так же адаптеров для бекенда, написанных на разный ЯП (typescript и go на данный момент) и обеспечивающих корректное создание структур json на стороне сервера.
Core-фичи
Начальное установление соединения с сервером и получение макета.
Поддержка нескольких сетевых протоколов для взаимодействия с сервером (http, websocket).
"Точечное" обновление состояния виджетов. Концепция "управляемых" виджетов - специальных компонентов, состояние которых может быть обновлено по требования бекенда (push-событие по ws или ответ на http-запрос).
Actions API. Специальный протокол, позволяющий серверу указывать зависимости для действия, связанного с виджетом. В данном случае, зависимостями могут выступать другие виджет, в которых могут храниться некоторые данные (TextInput, etc).
Возможность создания собственных виджетов, которые могут быть внедрены в фреймворк и использованы совместно с другими виджетами из базового набора.
Готовый для использования на бекенде набор функций, позволяющий строить иерархии виджетов. (По аналогии с composable functions из Jetpack Compose)
Не стану углубляться в детали реализации каждого из этих пунктов в целях экономии вашего времени. Ниже будут приведены ссылки на все упомянутые библиотеки. В репозиториях создано несколько примеров, которые смогут ближе познакомиться со способами использования фреймворка, а также страницы на GitHub Wiki.
Текущее состояние проекта
Во то время, когда вы читаете эту статью, уже опубликована версия v1.0.0 для всех входящих во фреймворк библиотек.
В базовой библиотеке используется следующий набор виджетов, которые Duit уже сейчас может корректно рисовать: Row, Column, Stack, SizedBox, ColoredBox, DecoratedBox, Container, Expanded, Padding, Positioned, Center, Text, TextField, ElevatedButton, Checkbox, Image (network, memory, asset). Библиотека будет пополняться.
Бекенд-адаптеры поддерживают весь список перечисленных выше виджетов. Так же на описаны структуры/интерфейсы для всех свойств, которые могут быть полезны для отрисовки на стороне Flutter (textStyle, padding etc).
Базовый сценарий использования.
Создаем экземпляр драйвер. Эта сущность отвечает за получение макета и взаимодействие в бекендом.
final driver = DUITDriver(
"/layout1",
transportOptions: HttpTransportOptions(
defaultHeaders: {"Content-Type": "application/json"},
baseUrl: "http://localhost:8999",
),
);
Вставляем виджет-ресивер там, где предполагается использования server driven виджет.
DuitViewHost(
context: context,
driver: driver,
placeholder: const CircularProgressIndicator(),
),
Формируем на стороне бека структуру виджета с помощью встроенных функций
func Example() []byte {
var holder duit_core.DuitView
builder := holder.Builder()
//Create root view from text widget. Assigning a text value and a style object
builder.RootFrom(duit_widget.TextUiElement[duit_color.ColorString](&duit_attributes.TextAttributes[duit_color.ColorString]{
Data: "Example",
Style: &duit_text_properties.TextStyle[duit_color.ColorString]{
FontWeight: duit_text_properties.W900,
},
}, "", false, nil))
//build json
value, err := builder.Build()
if err != nil {
fmt.Println(err.Error())
return []byte{}
}
return value
}
Реализуем эндпоинт, по которому драйвер сможет получить макет
eng.GET("/layout1", func(ctx *gin.Context) {
view := internal.Example()
ctx.Data(200, "application/json", view)
})
Этой настройки достаточно для того, чтобы отрисовать простой виджет Text с содержимым "Example".
P.S. и полезные ссылки
Создание этого велосипеда фреймворка было достаточно увлекательным занятием и я собираюсь и дальше его обновлять и поддерживать. Ведь многие интересные вызовы еще впереди!
Спасибо всем, кто прочитал до конца и всем тем, кто решит оставить свой комментарий под этой статьей! Отдельно будет интересно узнать о вашем опыте использования Duit в своих проектах.
Cсылки:
flutter_duit - библиотека для Flutter
duit_js - бекенд-адаптер на TypeScript
duit_go - бекенд-адаптер на Go