Кампании и рекомендации во Flutter
Готовый UI рендерит SDK; приложение передаёт контекст, размещает inline-блоки и обрабатывает навигацию. Для собственного UI используйте headless. Настройка кампании в кабинете: Кампании в приложении.
In-app
trackView() и triggerEvent() отправляют запрос активации, затем SDK получает контент подходящей кампании. Кандидаты проверяются по убыванию приоритета до первого с доступным корневым контентом. SDK учитывает задержку кампании, актуальность ответа, состояние контекста и presentation lock.
Поддерживаются модальное окно, bottom sheet, полноэкранный формат и SnackBar. Tooltip загружается через якорь. Контент с методом доставки JSON SDK сам не рисует.
Чтобы временно не показывать in-app, используйте presentation lock. Это не останавливает запросы контента.
Inline-блок
GravityInlineWidget(
selector: 'product_recommendations',
height: 280,
pageContext: const PageContext(
type: ContextType.product,
data: ['sku-123'],
location: 'app://product/sku-123',
),
showLoading: true,
loadingWidget: const Center(child: CircularProgressIndicator()),
)
Импорты для виджетов: package:flutter/material.dart и package:gravity_sdk/gravity_sdk.dart.
Без placeholderId выбирается корневой контент без placeholder. С placeholderId — корневой контент с точно совпавшим значением. Контент шагов не выбирается как корневой.
При ошибке, отсутствии подходящего контента или пустых товарах блок скрывается. onLoaded сообщает о загрузке, а не о фактической видимости и не заменяет impression.
Запрос выполняется при создании состояния виджета. При смене selector, товара, контекста или пользователя пересоздавайте блок с новым Key, например ValueKey('user-42:sku-123'); одна лишь замена параметров существующего состояния не гарантирует новую загрузку.
Группа inline-блоков
GravityInlineListWidget(
group: 'homepage_group',
height: 250,
pageContext: const PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
showIndicator: true,
)
Виджет получает кампании группы, выбирает корневые контенты и располагает их в горизонтальной карусели по variables.index. Доступны настройки загрузки и цвета индикаторов. Параметров rules, placeholderId и onLoaded у этого виджета нет.
Tooltip
GravityAnchor оборачивает элемент, относительно которого будет показана подсказка. Вызовите переданный onReady, когда якорь готов. Пример: сначала загрузить inline-контент, затем подсказку:
GravityAnchor(
selector: 'profile_tooltip',
pageContext: const PageContext(
type: ContextType.other,
data: [],
location: 'app://profile',
),
builder: (context, onReady) => GravityInlineWidget(
selector: 'profile_banner',
height: 120,
pageContext: const PageContext(
type: ContextType.other,
data: [],
location: 'app://profile',
),
onLoaded: onReady,
),
)
Для обычного собственного виджета сообщите о готовности после его layout через addPostFrameCallback. Не вызывайте onReady как побочный эффект каждого build().
Запросить подсказку вручную можно через fetchAnchorContent(context: ..., selector: ..., pageContext: ...). Якорь с тем же selector должен существовать. Не запускайте одновременно ручную загрузку и автоматическую загрузку того же якоря. Presentation lock на этот путь не действует.
Карточки товаров
Передайте ProductWidgetBuilder при инициализации. Slot.item — карта атрибутов из фида; названия полей и значения зависят от проекта. В примере используются name, price и imageUrl:
class ShopProductBuilder extends ProductWidgetBuilder {
const ShopProductBuilder({required this.onOpenProduct});
final void Function(Slot product) onOpenProduct;
@override
Widget build({
required BuildContext context,
required Slot product,
required CampaignContent content,
required Campaign campaign,
}) {
final item = product.item;
final imageUrl = item['imageUrl'];
return InkWell(
onTap: () {
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(product, content, campaign),
);
onOpenProduct(product);
},
child: SizedBox(
width: 160,
child: Column(
children: [
if (imageUrl is String)
Image.network(imageUrl, height: 100, fit: BoxFit.cover),
Text(item['name']?.toString() ?? ''),
Text(item['price']?.toString() ?? ''),
],
),
),
);
}
}
onOpenProduct подключите к реальной навигации вашего каталога при создании билдера. Если билдер не передан, SDK использует стандартную карточку.
В контейнере товаров SDK уже отслеживает видимость карточек, включая созданные вашим билдером: ≥50% один раз для слота. Здесь вручную отправляйте клик, а не дополнительный product visible impression. Если вы самостоятельно рисуете весь список товаров вне контейнера SDK, ответственность за оба события лежит на приложении.
Правила рекомендаций
Пример запроса с фильтром; поле и оператор должны поддерживаться вашим проектом:
final rules = [
RtRule(
type: 'filter',
conditions: [
RtRuleCondition(
field: 'category',
arguments: [
RtRuleArgument(action: 'in', value: ['shoes']),
],
),
],
),
];
final response = await GravitySDK.instance.getContentBySelector(
selector: 'category_recommendations',
pageContext: const PageContext(
type: ContextType.category,
data: ['shoes'],
location: 'app://category/shoes',
),
rules: rules,
);
rules поддерживаются в GravityInlineWidget и методах getContentBySelector/CampaignId/Group, а также SelectorWithDetails/CampaignIdWithDetails. У RtRule есть необязательные id и slots; у условия — field и arguments; у аргумента — action и список строк value. Универсального набора операторов SDK не задаёт.
Многошаговые кампании и формы
Встроенный рендерер поддерживает переходы по шагам, выбор вариантов, текстовые поля, ограничения ввода и условную видимость элементов. Корневой контент не имеет step; дополнительные контенты — имеют. Переход open_step обрабатывает SDK. Tooltip в качестве дополнительного шага не поддерживается.
In-app формы настраиваются в кампании; отдельный вызов отправки ответа в приложении не нужен. SDK проверяет обязательные видимые поля и длину ввода, сериализует ответы в строковые свойства CustomEvent и отправляет результат, включая campaignId и experienceId. При временной ошибке результат может остаться в офлайн-очереди.
После отправки переход по URL/диплинку всё равно требует вашего callback. Закрытие формы или переход UI не означает, что результат уже доставлен серверу. Не дублируйте событие отправки формы в callback приложения.
Кто отправляет engagement
Отправка использует tracking-события из ответа сервера. Если нужных events/обработчиков нет, SDK не может построить событие самостоятельно. Подробности — Аналитика собственного UI.