#
SDK Gravity Field (Flutter)
Gravity Field SDK — это лёгкий клиент для интеграции мобильных приложений (iOS / Android) с платформой персонализации и A/B-тестирования. Он позволяет запускать кампании без необходимости реализовывать собственную логику таргетинга, выбора и аналитики.
SDK работает по принципу "тонкого клиента": все решения принимает сервер (бекенд Gravity Field), а SDK:
- передаёт контекст текущего экрана,
- получает кампании и их содержимое в виде JSON или встроенных шаблонов,
- активирует показ
inlineиin-appкампаний - трекает взаимодействия пользователя (просмотры, клики, покупки и т.д.),
- и помогает фиксировать конверсии для аналитики и обучения моделей.
📦 Эта документация предназначена для мобильных разработчиков и инженеров, которые интегрируют SDK в приложение.
#
Добавление библиотеки в проект
- Из корня проекта вызовите команду:
flutter pub add gravity_sdk
- После добавления плагина в файле
pubspec.yamlпоявится строка с зависимостью:
dependencies:
gravity_sdk: ^0.10.2 # Замените на актуальную версию
- Добавьте импорт:
import 'package:gravity_sdk/gravity_sdk.dart';
Требования к версиям:
- Dart SDK:
>=3.6.0 - Flutter:
>=1.17.0
Для iOS:
Добавьте в файл ios/Runner/Info.plist ключ NSUserTrackingUsageDescription для запроса разрешения на отслеживание:
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
#
Быстрый старт
Этот раздел проведет вас через основные шаги интеграции SDK, от инициализации до запуска вашей первой кампании.
#
Шаг 1: Инициализация SDK
Сначала необходимо инициализировать SDK с вашим apiKey и section. Это лучше всего делать в функции main() вашего приложения. Критически важно подключить gravityEventCallback для обработки навигации и других действий.
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:gravity_sdk/gravity_sdk.dart';
void main() async {
// Обязательно до runApp
WidgetsFlutterBinding.ensureInitialized();
// Инициализация SDK
await GravitySDK.instance.initialize(
apiKey: 'YOUR_API_KEY',
section: 'YOUR_SECTION_ID',
// Обработка действий
gravityEventCallback: (event) {
print('Gravity SDK Event: ${event.runtimeType}');
// Обработка перехода по внешней ссылке
if (event is FollowUrlEvent) {
launchUrl(Uri.parse(event.url), mode: LaunchMode.externalApplication);
}
// Обработка перехода по диплинку
if (event is FollowDeeplinkEvent) {
// Здесь ваша логика навигации по диплинкам.
// Например, с использованием GoRouter:
// context.go(event.deeplink);
print('Follow Deeplink: ${event.deeplink}');
}
},
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ...
}
}
#
Почему важен gravityEventCallback?
SDK не выполняет навигацию самостоятельно. Когда пользователь нажимает на кнопку в кампании, SDK генерирует событие (например, FollowUrlEvent) и передает его в gravityEventCallback. Ваше приложение должно «поймать» это событие и выполнить соответствующее действие (открыть ссылку, перейти на другой экран). Без этого интерактивные элементы работать не будут.
Для открытия URL мы использовали пакет url_launcher. Не забудьте добавить его в ваш pubspec.yaml:
dependencies:
url_launcher: ^6.3.1
Подробнее о событиях см. раздел triggerEvent с CustomEvent.
#
Шаг 2: Идентификация пользователя
Когда пользователь входит в систему, важно связать его действия, совершенные анонимно, с его постоянным профилем. Для этого после авторизации отправляется LoginEvent. Это рекомендуемый способ идентификации.
// Вызывается после успешной авторизации пользователя
void onUserLoggedIn(BuildContext context, String rawPhoneNumber) {
// 1. Нормализуем номер телефона:
// "+7 (999) 000-00-00" -> "79990000000"
// "8 (999) 000-00-00" -> "79990000000" (для РФ/КЗ приводим к формату 7XXXXXXXXXX)
final normalizedPhone = normalizePhone(rawPhoneNumber);
// 2. Строим SHA-256 хеш нормализованной строки (UTF-8, lowercase hex)
final hashedPhone = sha256Hex(normalizedPhone);
final loginEvent = LoginEvent(
cuid: hashedPhone, // SHA-256 хеш нормализованного телефона
cuidType: 'phone_hash', // Стандартный тип идентификатора
);
GravitySDK.instance.triggerEvent(
context: context,
events: [loginEvent],
pageContext: PageContext(
type: ContextType.other,
data: [],
location: 'app://login',
),
);
}
Отправка LoginEvent "склеивает" анонимный профиль с профилем авторизованного пользователя, сохраняя всю историю его действий.
Подробнее о различных способах идентификации читайте в разделе
#
Шаг 3: Отслеживание просмотров экранов
Gravity Field принимает решение о показе персонализированного контента на основе контекста страницы, который передаёт SDK. Давайте отследим просмотр главной страницы.
// В коде виджета вашей главной страницы
void trackHomepageView(BuildContext context) {
GravitySDK.instance.trackView(
context: context,
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
);
}
Важно: если
PageContextсодержит значения из товарного фида, передавайте их без изменений относительно фида. Это относится к SKU, иерархии категорий,lngи другим feed-derived значениям. Регистр является частью значения: не приводите его к upper/lower case.
Если для этого события настроена in-app кампания, SDK автоматически покажет ее.
#
Почему SDK требует BuildContext?
SDK использует BuildContext для доступа к дереву виджетов и ThemeData вашего приложения. Это позволяет:
- Найти
ScaffoldMessengerдля показаSnackBar. - Использовать
Navigatorдля отображения диалогов, шторок и полноэкранных кампаний. - Наследовать стили (шрифты, цвета) из глобальной темы, чтобы кампании выглядели нативно.
#
Шаг 4: Отслеживание событий
Теперь отследим добавление товара в корзину. Это событие может запустить кампанию с товарными рекомендациями (например, "с этим товаром покупают").
void trackAddToCart(BuildContext context, String productId) {
final event = AddToCartEvent(
value: 99.99,
productId: productId,
quantity: 1,
currency: 'RUB',
);
GravitySDK.instance.triggerEvent(
context: context,
events: [event],
pageContext: PageContext(
type: ContextType.product,
data: [productId],
location: 'app://product/$productId',
),
);
}
Важно: если в событии или
PageContextпередаются значения, которые должны матчиться с товарным фидом, используйте их в точности как в фиде. Это относится кproductId,cart[*].productId, SKU вdata, категориям,lngи другим feed-derived значениям. Регистр должен совпадать с фидом.
#
Шаг 5: Отображение inline-кампаний
Для отображения рекомендаций прямо в верстке страницы (например, блок "Персональные рекомендации") используйте виджет GravityInlineWidget.
// В методе build вашего виджета
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Inline Recommendations')),
body: ListView(
children: [
// ... другие виджеты
GravityInlineWidget(
selector: 'homepage-recommendations',
height: 250, // Обязательно задайте высоту для блока
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
),
// ... другие виджеты
],
),
);
}
Виджет сам загрузит и отобразит релевантный контент по селектору homepage-recommendations.
#
Инициализация и конфигурация
#
initialize()
Основной метод для настройки SDK. Вызывается один раз при старте приложения.
Future<void> initialize({
required String apiKey,
required String section,
ProductWidgetBuilder? productWidgetBuilder,
GravityEventCallback? gravityEventCallback,
GravityContentCallback? gravityContentCallback,
LogLevel logLevel = LogLevel.info,
});
apiKey(required): Ваш уникальный ключ API.section(required): Идентификатор секции вашего проекта.productWidgetBuilder: Опциональный билдер для кастомизации карточек товаров. См. разделРабота с контентом .gravityEventCallback: Опциональный колбэк для событий SDK и действий пользователя внутри кампаний. См. разделОбработка обратных вызовов (Callbacks) .gravityContentCallback: Опциональный колбэк для headless-сценариев, где нужно получитьGravityDataResponse<ContentResponse>вместе с исходным JSON.logLevel: Уровень детализации логов SDK. По умолчаниюLogLevel.info. См. разделНастройка логирования .
#
setOptions()
Позволяет задать глобальные настройки для всех последующих запросов.
void setOptions({
Options? options,
ContentSettings? contentSettings,
String? proxyUrl,
bool? isFetchContentOnTrack,
});
options: Настройки для управления поведением запросов.contentSettings: Настройки для управления получаемым контентом.proxyUrl: URL прокси-сервера для отправки запросов.isFetchContentOnTrack: Включает или отключает автоматическую догрузку контента послеtrackView(...)иtriggerEvent(...). По умолчаниюtrue.
Пример:
GravitySDK.instance.setOptions(
options: Options(
isReturnUserInfo: true, // Возвращать информацию о пользователе в ответах
isImplicitImpression: true, // Автоматически отправлять событие показа
),
contentSettings: ContentSettings(
skusOnly: false, // Возвращать полную информацию о продуктах, а не только SKU
fields: ['name', 'price', 'imageUrl'], // Запросить конкретные поля
),
);
#
Настройка логирования
При инициализации SDK вы можете указать уровень детализации логов с помощью параметра logLevel. Это полезно для отладки интеграции.
await GravitySDK.instance.initialize(
apiKey: 'YOUR_API_KEY',
section: 'YOUR_SECTION_ID',
logLevel: LogLevel.debug, // Устанавливаем максимальный уровень детализации
);
Доступные уровни LogLevel:
- LogLevel.none: Логирование полностью отключено.
- LogLevel.error: Только ошибки.
- LogLevel.warn: Ошибки и предупреждения.
- LogLevel.info: (По умолчанию) Информационные сообщения, ошибки и предупреждения.
- LogLevel.debug: Максимальная детализация, включая тела запросов и ответов.
#
Идентификация пользователя
SDK поддерживает два подхода к идентификации: автоматический (управляется SDK) и ручной (управляется вашим приложением).
#
Стандарт CUID на основе телефона (phone_hash)
Во всех интеграциях рекомендуется использовать единый идентификатор пользователя — SHA-256 хеш нормализованного мобильного телефона:
- Перед хешированием номер очищается от всех символов, кроме цифр.
- Для РФ/КЗ номер приводится к формату
7XXXXXXXXXX(без+, пробелов, скобок и дефисов). - Для других стран используется международный формат без
+и разделителей. - Хеш считается по строке в кодировке UTF-8, результат — lowercase hex.
- В
LoginEvent.cuidTypeвсегда указывается строка'phone_hash'.
Тот же хеш и тип должны использоваться:
- в Web-интеграции (JavaScript-событие
Login), - в Server-Side API (
/ssapi/event), - при импорте офлайн-данных.
- Если в CDP или DWH уже рассчитывается этот идентификатор по тому же алгоритму, во всех типах интеграции (включая Flutter SDK) необходимо передавать именно тот хеш, который используется в CDP, чтобы обеспечить единый идентификатор ключ пользователя.
#
Автоматическая идентификация (SDK-managed)
Это подход по умолчанию. При первом запросе SDK получает от сервера уникальный uid (user ID) и ses (session ID) и сохраняет их на устройстве. Все последующие запросы будут использовать эти идентификаторы.
Чтобы связать анонимный профиль с профилем авторизованного пользователя, после входа в систему отправьте LoginEvent.
// Вызывается после успешной авторизации
void onUserLoggedIn(BuildContext context, String rawPhoneNumber) {
final normalizedPhone = normalizePhone(rawPhoneNumber);
final hashedPhone = sha256Hex(normalizedPhone);
final loginEvent = LoginEvent(
cuid: hashedPhone,
cuidType: 'phone_hash', // Стандартный тип идентификатора: SHA-256 хеш телефона
);
GravitySDK.instance.triggerEvent(
context: context,
events: [loginEvent],
pageContext: PageContext(
type: ContextType.other,
data: [],
location: 'app://login',
),
);
}
#
Ручная идентификация
Если ваше приложение уже управляет ID пользователей и сессий, вы можете передавать их в SDK напрямую с помощью метода setUser.
#
setUser()
Устанавливает custom (ваш ID пользователя) и ses (ваш ID сессии) для всех последующих запросов.
void setUser(String userId, String sessionId);
Пример:
// Вызывается при старте сессии, если ID уже известны
GravitySDK.instance.setUser('user-from-my-system-42', 'session-from-my-system-xyz');
В этом режиме SDK не будет использовать автоматически сгенерированный uid.
#
PageContext
PageContext описывает, где находится пользователь в приложении и в каком бизнес-контексте выполняется запрос.
Этот объект используется в ключевых вызовах Flutter SDK:
для передачи просмотров экранов;trackView(...) для передачи пользовательских действий;triggerEvent(...) для ручного получения контента;getContentBySelector(...) для загрузки группы inline-кампаний.getContentByGroup(...)
Корректное заполнение PageContext влияет и на аналитику, и на подбор контента, и на активацию кампаний.
#
Структура PageContext
class PageContext {
final ContextType type;
final List<String> data;
final String location;
final String? lng;
final int? pageNumber;
final String? referrer;
final Map<String, String>? utm;
final Map<String, Object> attributes;
}
Бизнес-логика заполнения PageContext соответствует Page context.
Рекомендуемая схема:
HOMEPAGE:data = [].PRODUCT: вdataпередается SKU товара ровно как в товарном фиде, без нормализации и с тем же регистром.CART: вdataпередаются SKU всех товаров, находящихся в корзине, ровно как в товарном фиде, без нормализации и с тем же регистром.CATEGORY: вdataпередается полная иерархия категорий от самой широкой до самой узкой ровно как в товарном фиде, без нормализации и с тем же регистром.SEARCH: вdataпередается поисковый запрос одной строкой. Для пустого поиска передается пустой список.OTHER: используется только для экранов, которые не подходят под остальные типы.
Для любых значений в PageContext, которые должны матчиться с товарным фидом, действует единое правило: передавайте их идентично фиду. Нельзя менять написание, переименовывать значения или приводить их к upper/lower case.
lng используется для мультирегиональности. Это позволяет отдавать корректные региональные данные по товарам: цены, доступность и остатки.
Например, если пользователь находится в Новосибирске, значение lng можно использовать для того, чтобы в рекомендациях участвовали только товары, реально доступные в Новосибирске.
Важно:
- значения
lngв контексте и в товарном фиде должны совпадать полностью, включая регистр; lng, как и любые другие feed-derived значения, нужно передавать без преобразования к upper/lower case;lngнужно передавать только если проект использует региональные варианты данных в фиде.
SDK автоматически дополняет attributes служебными значениями app_version, sdk_version и app_platform.
См. также:
trackView(...)triggerEvent(...)getContentBySelector(...)getContentByGroup(...)
#
Отслеживание просмотров экранов
#
trackView(...)
trackView(...) передает в Gravity Field факт просмотра экрана. Этот вызов используется и для трекинга пользовательского поведения, и для сценариев, в которых показ кампании зависит от контекста страницы.
Future<void> trackView({
required BuildContext context,
required PageContext pageContext,
});
context:BuildContextтекущего экрана.pageContext: корректно заполненный .PageContext
Пример:
GravitySDK.instance.trackView(
context: context,
pageContext: PageContext(
type: ContextType.product,
data: ['product-sku-123'],
location: 'app://product/123',
),
);
Если для переданного контекста настроена in-app кампания, SDK может загрузить и показать ее автоматически.
#
Почему SDK требует BuildContext?
Flutter SDK использует BuildContext для показа SnackBar, модальных окон, bottom sheet и полноэкранных кампаний, а также для доступа к навигации и теме приложения.
#
Трекинг событий
События описывают действия пользователя: покупку, добавление в корзину, авторизацию и другие сценарии, которые должны попадать в аналитику или запускать кампании.
Бизнес-логика заполнения событий соответствует Настройке передачи событий, а Flutter SDK предоставляет типы и методы для их отправки.
Для e-commerce сценариев события PurchaseEvent и AddToCartEvent обязательны. Остальные события внедряются по релевантности вашему сценарию.
Для всех полей событий, которые должны матчиться с товарным фидом, действует то же правило, что и для PageContext: передавайте значения идентично фиду. Это относится к productId, cart[*].productId и другим feed-derived значениям. Регистр является частью значения.
#
triggerEvent(...)
triggerEvent(...) передает в Gravity Field пользовательские действия. Этот вызов нужен и для аналитики, и для сегментации, и для сценариев, в которых событие может активировать кампанию.
Future<void> triggerEvent({
required BuildContext context,
required List<TriggerEvent> events,
required PageContext pageContext,
});
context:BuildContextтекущего экрана.events: список событий для отправки.pageContext: корректно заполненный .PageContext
Пример:
GravitySDK.instance.triggerEvent(
context: context,
events: [
AddToCartEvent(
value: 1500.0,
productId: 'sku-abc-1',
quantity: 1,
currency: 'RUB',
),
],
pageContext: PageContext(
type: ContextType.product,
data: ['sku-abc-1'],
location: 'app://product/sku-abc-1',
),
);
#
Покупка (PurchaseEvent)
Отправляется после успешного завершения заказа.
final purchaseEvent = PurchaseEvent(
uniqueTransactionId: 'ORDER-12345',
value: 2550.75,
currency: 'RUB',
cart: [
CartItem(productId: 'sku-123', quantity: 1, itemPrice: 100.50),
CartItem(productId: 'sku-456', quantity: 2, itemPrice: 1225.125),
],
);
Правила заполнения:
uniqueTransactionIdдолжен быть уникальным для каждой покупки;value— полная сумма заказа;currencyопциональна, но обязательна для мультивалютных проектов;cartсодержит фактический состав заказа;- каждый
cart[*].productIdдолжен совпадать со SKU в товарном фиде полностью, включая регистр; - товары в
cartрекомендуется передавать в порядке добавления: от самых старых к самым новым; itemPrice— стоимость одной единицы товара после применения скидок.
#
Добавление в корзину (AddToCartEvent)
Отправляйте событие в момент фактического добавления товара в корзину.
final addToCartEvent = AddToCartEvent(
value: 1500.0,
productId: 'sku-abc-1',
quantity: 1,
currency: 'RUB',
);
Правила заполнения:
value— сумма, добавляемая в корзину этим действием;quantity— количество единиц, добавленных именно этим действием;productIdдолжен совпадать со SKU в товарном фиде полностью, включая регистр;currencyопциональна, но обязательна для мультивалютных проектов;cart, если передается, должен содержать актуальное состояние корзины, включая только что добавленный товар. Всеcart[*].productIdдолжны совпадать со SKU в товарном фиде полностью, включая регистр.
#
Удаление из корзины (RemoveFromCartEvent) и синхронизация корзины (SyncCartEvent)
RemoveFromCartEventотправляйте в момент удаления товара из корзины или уменьшения количества;valueвRemoveFromCartEvent— сумма удаляемых единиц товара;quantityвRemoveFromCartEvent— количество единиц, удаленных этим действием;SyncCartEventиспользуйте, когда нужно передать актуальное состояние корзины целиком;valueвSyncCartEvent— общая стоимость актуального состава корзины;productIdвRemoveFromCartEventдолжен совпадать со SKU в товарном фиде полностью, включая регистр;cartвSyncCartEventдолжен содержать полное текущее состояние корзины. Всеcart[*].productIdдолжны совпадать со SKU в товарном фиде полностью, включая регистр.
#
Вход в систему (LoginEvent)
final loginEvent = LoginEvent(
cuid: sha256Hex(normalizePhone(rawPhoneNumber)),
cuidType: 'phone_hash',
);
Поле hashedEmail опционально. Если email не является обязательным в вашем процессе регистрации или логина, его можно не передавать.
#
Добавление в избранное (AddToWishlistEvent)
final addToWishlistEvent = AddToWishlistEvent(
value: 1500.0,
productId: 'sku-abc-1',
);
productId в AddToWishlistEvent должен совпадать со SKU в товарном фиде полностью, включая регистр.
#
Кастомное событие (CustomEvent)
final customEvent = CustomEvent(
type: 'survey-completed-v1',
name: 'Survey completed',
customProps: {
'surveyId': 'summer-2025-feedback',
'rating': '5',
},
);
#
Передача статуса Push-уведомлений
Если таргетинг кампаний зависит от статуса push-разрешения, передавайте его в SDK.
#
setNotificationPermissionStatus()
void setNotificationPermissionStatus(NotificationPermissionStatus status);
Допустимые значения:
NotificationPermissionStatus.grantedNotificationPermissionStatus.deniedNotificationPermissionStatus.unknown
Пример:
import 'package:firebase_messaging/firebase_messaging.dart';
Future<NotificationPermissionStatus> getNotificationStatus() async {
final settings = await FirebaseMessaging.instance.getNotificationSettings();
switch (settings.authorizationStatus) {
case AuthorizationStatus.authorized:
case AuthorizationStatus.provisional:
return NotificationPermissionStatus.granted;
case AuthorizationStatus.denied:
return NotificationPermissionStatus.denied;
default:
return NotificationPermissionStatus.unknown;
}
}
final status = await getNotificationStatus();
GravitySDK.instance.setNotificationPermissionStatus(status);
#
Работа с контентом
Flutter SDK поддерживает три основных режима работы с контентом:
- in-app кампании, которые SDK показывает самостоятельно;
- inline кампании, которые встраиваются в экран приложения;
- JSON/manual rendering, когда приложение само интерпретирует ответ и строит UI.
#
In-App кампании
SDK автоматически отображает in-app кампании, если они приходят в ответ на trackView(...) или triggerEvent(...).
Приложение отвечает за:
- корректный
;PageContext - корректный
BuildContext; - обработку действий пользователя через
gravityEventCallback.
#
GravityInlineWidget
Используйте GravityInlineWidget для загрузки и отображения одного inline-блока по selector.
GravityInlineWidget(
selector: 'homepage-recs',
height: 250,
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
)
Для мультивиджет-кампаний используйте placeholderId.
#
Параметры виджета
#
GravityInlineListWidget
GravityInlineListWidget используется для отображения нескольких inline-элементов из одной группы.
GravityInlineListWidget(
group: 'homepage-group',
height: 250,
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
)
Этот виджет использует публичный метод getContentByGroup(...)
#
ProductWidgetBuilder
Если нужно полностью контролировать отображение карточек товара, реализуйте ProductWidgetBuilder.
Важно:
- SDK автоматически отслеживает показ товара;
- клик по товару в кастомной карточке нужно отправлять вручную через
sendProductEngagement(...).
class CustomProductWidgetBuilder extends ProductWidgetBuilder {
@override
Widget build({
required BuildContext context,
required Slot product,
required CampaignContent content,
required Campaign campaign,
}) {
return GestureDetector(
onTap: () {
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(product, content, campaign),
);
},
child: Card(
child: Text(product.item['name'] as String? ?? ''),
),
);
}
}
#
JSON и manual rendering
Используйте JSON-режим, если приложение должно само интерпретировать ответ и строить UI без встроенных компонентов SDK.
#
getContentBySelector(...)
Future<ContentResponse> getContentBySelector({
required String selector,
required PageContext pageContext,
});
Для getContentBySelector(...) требуется корректно заполненный PageContext
Пример:
final response = await GravitySDK.instance.getContentBySelector(
selector: 'homepage-inline-banner',
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://home',
),
);
if (response.data.isNotEmpty) {
final campaign = response.data.first;
final variation = campaign.payload.firstOrNull;
final content = variation?.contents.firstOrNull;
// Отрисуйте content в своем UI
}
#
getContentByGroup(...)
Future<ContentResponse> getContentByGroup({
required String group,
required PageContext pageContext,
});
Этот метод используется для ручного получения группы кампаний и лежит в основе GravityInlineListWidget.
#
Headless API
Flutter SDK также предоставляет дополнительные публичные методы для headless-сценариев:
getContentBySelectorWithDetails(...)getContentByCampaignIdWithDetails(...)trackViewNoShow(...)triggerEventNoShow(...)gravityContentCallback
Используйте их, если нужно получить GravityDataResponse<ContentResponse> вместе с исходным JSON или получить контент без автоматического показа UI.
#
Трекинг взаимодействий (engagement)
Engagement-события позволяют фиксировать показы и клики по контенту и товарам.
#
Какие события SDK отправляет автоматически
Для встроенных UI-компонентов SDK сам отправляет базовые события показа.
Для кастомного UI вручную нужно отправлять только те взаимодействия, которые SDK не может определить сам.
#
sendContentEngagement()
void sendContentEngagement(ContentEngagement engagement)
Поддерживаемые типы:
ContentImpressionEngagement(content, campaign)ContentVisibleImpressionEngagement(content, campaign)ContentCloseEngagement(content, campaign)
#
sendProductEngagement()
void sendProductEngagement(ProductEngagement engagement)
Поддерживаемые типы:
ProductClickEngagement(slot, content, campaign)ProductVisibleImpressionEngagement(slot, content, campaign)
#
Когда нужно отправлять engagement вручную
- при полностью ручном JSON-рендеринге;
- при кастомном рендеринге карточек товара через
ProductWidgetBuilder; - в других сценариях, где вы сами контролируете жизненный цикл видимости и клика.
#
Откуда брать slot, content и campaign
Чаще всего эти объекты берутся из ответа getContentBySelector(...).
final response = await GravitySDK.instance.getContentBySelector(
selector: 'homepage-inline-banner',
pageContext: pageContext,
);
final campaign = response.data.firstOrNull;
final variation = campaign?.payload.firstOrNull;
final content = variation?.contents.firstOrNull;
final slot = content?.products?.slots?.firstOrNull;
После этого можно отправлять engagement:
if (content != null && campaign != null) {
GravitySDK.instance.sendContentEngagement(
ContentVisibleImpressionEngagement(content, campaign),
);
}
if (slot != null && content != null && campaign != null) {
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign),
);
}
Если вы используете ProductWidgetBuilder, SDK сам передает вам slot, content и campaign в метод build(...).
#
Справочник по событиям (TriggerEvent)
#
Обработка обратных вызовов (Callbacks)
Подпишитесь на события SDK, передав функцию gravityEventCallback в initialize.
#
gravityEventCallback
gravityEventCallback получает события SDK, связанные с загрузкой контента, показами и действиями пользователя внутри кампаний.
#
gravityContentCallback
gravityContentCallback предназначен для headless-сценариев и получает GravityDataResponse<ContentResponse> вместе с исходным JSON.
#
Справочник по событиям (TrackingEvent)
Пример обработки:
gravityEventCallback: (event) {
if (event is FollowUrlEvent) {
// Открыть URL с помощью url_launcher
} else if (event is FollowDeeplinkEvent) {
// Выполнить навигацию с помощью вашего роутера
} else if (event is RequestPushEvent) {
// Запросить разрешение на push-уведомления
}
}
RequestPushEvent — это сигнал приложению запросить push-permission. Flutter SDK сам системный диалог не открывает.
#
FAQ / Troubleshooting
Q: В чем разница между trackView и triggerEvent?
A: Оба метода могут инициировать показ кампании, но их роль шире. trackView(...) передает факт просмотра экрана и используется для аналитики поведения на экранах. triggerEvent(...) передает конкретные действия пользователя и используется для аналитики, сегментации и сценариев, активируемых событиями.
Q: Почему GravityInlineWidget не отображается или имеет нулевую высоту?
A: Самая частая причина — для GravityInlineWidget не задан параметр height.
Q: Почему клик по кнопке в кампании не приводит к переходу?
A: Не обработаны FollowUrlEvent или FollowDeeplinkEvent в gravityEventCallback. SDK не выполняет навигацию сам.
Q: Что произойдет, если SDK не сможет получить кампанию от сервера?
A: SDK обработает ошибку внутри. Inline-компонент просто не будет отображен, а in-app кампания не будет показана.
Q: Почему в ProductWidgetBuilder нужно самому отправлять клик?
A: Потому что вы управляете кастомным UI. SDK не знает, какой именно элемент внутри вашей карточки следует считать кликом по товару.