Итак, здравствуйте! Меня зовут Никита Синявин, я разработчик мобильных приложений. Сейчас наша команда использует 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
