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

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


  1. PackRuble
    20.12.2023 14:52

    А есть ещё такая штука, как rfw. Это имеет какие-то общие грани с вашим проектом?


    1. LesleySin Автор
      20.12.2023 14:52

      Если сосредоточить внимание на том, что мы может определить где-то декларативное описание иерархии виджетов, то да, пересечения точно есть. Так же есть похожие механизмы под капотом, например ArgumentDecoders. Можно сказать, что они решают одну и ту же задачу, в широком смысле.

      Но я бегло глянул описание rfw и код и все-таки склоняюсь к тому, что отличий сильно больше, чем сходств. Что по способу использования, что по внутреннему устройству.

      Спасибо за комментарий!