#
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.
#
resetUser()
Сбрасывает текущего пользователя и сохранённую SDK-сессию. Используйте метод перед переключением на другой пользовательский профиль, если приложение работает с несколькими клиентскими профилями без переинициализации SDK.
Future<void> resetUser() async;
Пример:
// Перед открытием профиля другого клиента
await GravitySDK.instance.resetUser();
GravitySDK.instance.setUser('next-client-id', 'next-client-session-id');
После resetUser() следующие запросы не используют прежние uid и ses.
#
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.
Начиная с 0.16.0, для tooltip-кампаний нужен дополнительный шаг: целевой элемент интерфейса должен быть зарегистрирован через GravityAnchor, иначе SDK не сможет привязать tooltip к экрану.
#
GravityInlineWidget
Используйте GravityInlineWidget для загрузки и отображения одного inline-блока по selector.
GravityInlineWidget(
selector: 'homepage-recs',
height: 250,
rules: [
RtRule(
type: 'filter',
conditions: [
RtRuleCondition(
field: 'category',
arguments: [
RtRuleArgument(action: 'in', value: ['shoes']),
],
),
],
),
],
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
)
Для мультивиджет-кампаний используйте placeholderId.
#
Параметры виджета
#
GravityAnchor и tooltip-кампании
Начиная с версии 0.16.0, Flutter SDK поддерживает tooltip как отдельный delivery method. В отличие от modal, full screen, bottom sheet и snackbar, tooltip не показывается сам по себе поверх всего экрана, а привязывается к конкретному элементу интерфейса.
Для этого используется GravityAnchor.
#
Когда нужен GravityAnchor
Используйте GravityAnchor, если:
- серверная кампания должна отобразиться как tooltip рядом с кнопкой, баннером или другим элементом UI;
- показ нужно выполнить только после того, как нужный элемент уже отрисован;
- требуется связать selector кампании с конкретной точкой на экране.
#
Как работает связка GravityAnchor
GravityAnchor регистрирует элемент по selector внутри SDK. После этого SDK может запросить контент именно для этого selector и, если сервер вернул tooltip-кампанию, показать её рядом с зарегистрированным anchor.
Типичный flow:
- Оберните нужный виджет в
GravityAnchor. - Вызовите
onReady, когда вложенный виджет уже можно измерить и использовать как anchor. - SDK загрузит контент по selector и покажет tooltip, если для него пришла подходящая кампания.
#
Пример
GravityAnchor(
selector: 'profile_tooltip',
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
builder: (context, onReady) {
return GravityInlineWidget(
selector: 'profile_inline',
height: 120,
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
onLoaded: onReady,
);
},
)
#
Важные детали интеграции
selectorуGravityAnchorдолжен совпадать с selector, для которого backend возвращает tooltip-кампанию.- Anchor должен существовать в момент показа. Если элемент ещё не отрисован, SDK не сможет корректно вычислить позицию tooltip.
- Для
GravityInlineWidgetначиная с0.16.0есть колбэкonLoaded, который удобно использовать как момент готовности anchor. - Если tooltip должен быть привязан не к inline-контенту, а к вашему собственному виджету, внутри
builderможно вернуть любой widget и вызватьonReadyпосле его фактической готовности.
#
Что можно настраивать в tooltip
SDK поддерживает параметры tooltip, которые приходят в payload кампании:
- позицию относительно anchor;
- выравнивание;
- параметры стрелки;
- максимальную ширину;
- поведение при клике вне tooltip;
- автозакрытие.
Эти параметры обрабатываются SDK автоматически. Со стороны приложения главное — корректно зарегистрировать anchor и передать верный pageContext.
#
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,
List<RtRule>? rules,
});
Для 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
}
#
getContentByCampaignId(...)
Future<ContentResponse> getContentByCampaignId({
required String campaignId,
required PageContext pageContext,
List<RtRule>? rules,
});
Используйте метод, если нужно получить контент конкретной кампании по её идентификатору.
#
getContentByGroup(...)
Future<ContentResponse> getContentByGroup({
required String group,
required PageContext pageContext,
List<RtRule>? rules,
});
Этот метод используется для ручного получения группы кампаний и лежит в основе GravityInlineListWidget.
#
Правила рекомендаций (rules)
Параметр rules позволяет передать в запрос контента правила рекомендаций, например фильтры, выбранные пользователем в товарной выдаче. Правила поддерживаются в:
GravityInlineWidget;getContentBySelector(...);getContentByCampaignId(...);getContentByGroup(...);getContentBySelectorWithDetails(...);getContentByCampaignIdWithDetails(...).
Структура моделей:
class RtRule {
final int? id;
final String type;
final List<int>? slots;
final List<RtRuleCondition> conditions;
}
class RtRuleCondition {
final String field;
final List<RtRuleArgument> arguments;
}
class RtRuleArgument {
final String action;
final List<String> value;
}
Короткий пример:
final rules = [
RtRule(
type: 'filter',
conditions: [
RtRuleCondition(
field: 'category',
arguments: [
RtRuleArgument(action: 'in', value: ['shoes']),
],
),
RtRuleCondition(
field: 'price',
arguments: [
RtRuleArgument(action: 'lte', value: ['10000']),
],
),
],
),
];
final response = await GravitySDK.instance.getContentBySelector(
selector: 'category-recommendations',
pageContext: pageContext,
rules: rules,
);
Конкретные значения type, field и action должны соответствовать правилам рекомендаций, настроенным для вашего проекта.
#
Headless API
Flutter SDK также предоставляет дополнительные публичные методы для headless-сценариев:
getContentBySelectorWithDetails({ required String selector, required PageContext pageContext, List<RtRule>? rules })getContentByCampaignIdWithDetails({ required String campaignId, required PageContext pageContext, List<RtRule>? rules })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 не знает, какой именно элемент внутри вашей карточки следует считать кликом по товару.
See also
Ниже описаны шаги для внедрения рекламной платформы на вашем сайте или приложении.
В iOS SDK 0.0.9 зафиксирован публичный сценарий для рекомендательных виджетов через getContentBySelector(...)
Этот гайд предназначен для Flutter-разработчиков, которым нужен полный контроль над HTTP-вызовами, состоянием, рендерингом и трекингом кампаний...
Во Flutter SDK добавлена возможность сбрасывать сохранённого пользователя и сессию перед новым запросом рекомендаций.
Gravity Field теперь помогает запускать персонализированные кампании и вовлекающие сценарии внутри мобильных приложений
В GravityInlineWidget для Flutter SDK добавлен новый опциональный параметр placeholderId.