#
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.9.8 # Замените на актуальную версию
- Добавьте импорт:
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`:
```yaml
dependencies:
url_launcher: ^6.3.1
Подробнее о событиях см. раздел triggerEvent
с CustomEvent
.
#
Шаг 2: Идентификация пользователя
Когда пользователь входит в систему, важно связать его действия, совершенные анонимно, с его постоянным профилем. Для этого после авторизации отправляется LoginEvent
. Это рекомендуемый способ идентификации.
// Вызывается после успешной авторизации пользователя
void onUserLoggedIn(BuildContext context, String userCustomId) {
final loginEvent = LoginEvent(cuid: userCustomId, cuidType: 'mySystemUserId');
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',
),
);
}
Если для этого события настроена 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',
),
);
}
#
Шаг 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,
});
apiKey
(required): Ваш уникальный ключ API.section
(required): Идентификатор секции вашего проекта.productWidgetBuilder
: Опциональный билдер для кастомизации виджетов продуктов. (См. разделРабота с контентом ).gravityEventCallback
: Опциональный колбэк для получения уведомлений о событиях SDK. (См. разделОбработка обратных вызовов (Callbacks) ).
#
setOptions()
Позволяет задать глобальные настройки для всех последующих запросов.
void setOptions({
Options? options,
ContentSettings? contentSettings,
String? proxyUrl,
});
options
: Настройки для управления поведением запросов.contentSettings
: Настройки для управления получаемым контентом.proxyUrl
: URL прокси-сервера для отправки запросов.
Пример:
GravitySDK.instance.setOptions(
options: Options(
isReturnUserInfo: true, // Возвращать информацию о пользователе в ответах
isImplicitImpression: true, // Автоматически отправлять событие показа
),
contentSettings: ContentSettings(
skusOnly: false, // Возвращать полную информацию о продуктах, а не только SKU
fields: ['name', 'price', 'imageUrl'], // Запросить конкретные поля
),
);
#
Идентификация пользователей
SDK поддерживает два подхода к идентификации: автоматический (управляется SDK) и ручной (управляется вашим приложением).
#
Автоматическая идентификация (SDK-managed)
Это подход по умолчанию. При первом запросе SDK получает от сервера уникальный uid
(user ID) и ses
(session ID) и сохраняет их на устройстве. Все последующие запросы будут использовать эти идентификаторы.
Чтобы связать анонимный профиль с профилем авторизованного пользователя, после входа в систему отправьте LoginEvent
.
// Вызывается после успешной авторизации
void onUserLoggedIn(BuildContext context, String userCustomId) {
final loginEvent = LoginEvent(
cuid: userCustomId,
cuidType: 'mySystemUserId', // Укажите тип вашего идентификатора
);
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
.
#
Передача контекста
Gravity Field принимает решение о показе персонализированного контента на основе контекста страницы, который передаёт SDK.
Контекст страницы позволяет Gravity Field понять:
- Где сейчас находится пользователь в приложении
- Какие товары или категории он просматривает
- В каком регионе или в каких условиях пользователь находится
#
trackView(...)
Отправляет событие просмотра экрана. В ответ может прийти кампания для показа.
Future<void> trackView({
required BuildContext context,
required PageContext pageContext,
});
context
:BuildContext
текущего экрана.pageContext
: Контекст страницы, который описывает, где находится пользователь.
Пример:
// На экране продукта
GravitySDK.instance.trackView(
context: context,
pageContext: PageContext(
type: ContextType.product,
data: ['product-sku-123'], // SKU продукта
location: 'app://product/123',
),
);
#
PageContext
Ключевая модель для описания местоположения и контекста пользователя.
💡 Типы контекста и соответсвующие для них data описаны здесь: Page context
#
Трекинг событий
Трекинг событий позволяет отправлять информацию о действиях пользователя — таких как покупка, добавление товара в корзину, авторизация и другие события. Эти данные используются Gravity Field для аналитики, построения сегментов и запуска кампаний, активируемых по событиям.
Трекинг событий применяется, когда кампании должны срабатывать не по просмотру страницы, а по пользовательскому действию, и также для сбора аналитики.
События фиксируются для того, чтобы измерить поведение пользователя в разных вариациях кампаний: например, узнать, какой из вариантов чекаута приводит к более высокой конверсии. Это позволяет анализировать эффективность персонализации и принимать решения на основе данных.
Платформа поддерживает как системные (предопределённые) события, так и произвольные пользовательские события.
- Системные события — это события с зафиксированными
type
, которые платформа распознаёт и может использовать для активации кампаний или анализа (например,purchase
,add_to_cart
,login
). - Пользовательские события — разработчик может отправлять любые события с кастомным
type
. Такие события будут использоваться для аналитики и построения собственных отчётов или логики в Gravity Field.
#
triggerEvent(...)
Отправляет одно или несколько событий о действиях пользователя.
Future<void> triggerEvent({
required BuildContext context,
required List<TriggerEvent> events,
required PageContext pageContext,
});
context
:BuildContext
текущего экрана.events
: Список событий для отправки. SDK предоставляет множество готовых классов событий (PurchaseEvent, LoginEvent и др.).pageContext
: Контекст страницы.
Ниже приведены примеры кода для отслеживания наиболее распространенных бизнес-событий с помощью triggerEvent().
#
Покупка (PurchaseEvent
)
Событие отправляется после успешного завершения заказа.
final purchaseEvent = PurchaseEvent(
uniqueTransactionId: 'ORDER-12345', // Уникальный ID транзакции
value: 2550.75,
currency: 'RUB',
cart: [
CartItem(productId: 'sku-123', quantity: 1, itemPrice: 100.50),
CartItem(productId: 'sku-456', quantity: 1, itemPrice: 50.00),
CartItem(productId: 'sku-abc-1', quantity: 1, itemPrice: 1500.00),
CartItem(productId: 'sku-def-2', quantity: 2, itemPrice: 525.375),
],
);
GravitySDK.instance.triggerEvent(
context: context,
events: [purchaseEvent],
pageContext: PageContext(
type: ContextType.other,
data: [],
location: 'app://checkout/success',
),
);
#
Добавление в корзину (AddToCartEvent
)
Отправляется, когда пользователь добавляет товар в корзину.
final addToCartEvent = AddToCartEvent(
value: 1500.00,
productId: 'sku-abc-1',
quantity: 1,
currency: 'RUB',
);
GravitySDK.instance.triggerEvent(
context: context,
events: [addToCartEvent],
pageContext: PageContext(
type: ContextType.product,
data: ['sku-abc-1'],
location: 'app://product/sku-abc-1',
),
);
#
Вход в систему (LoginEvent
)
Отправляется после успешной аутентификации пользователя.
final loginEvent = LoginEvent(
// Передайте один из идентификаторов
cuid: 'customer-12345',
cuidType: 'mySystemUserId',
// hashedEmail: '...' // или хешированный email
);
GravitySDK.instance.triggerEvent(
context: context,
events: [loginEvent],
pageContext: PageContext(
type: ContextType.other,
data: [],
location: 'app://login',
),
);
#
Кастомное событие (CustomEvent
)
Для отслеживания любых других действий, не покрытых стандартными событиями.
final customEvent = CustomEvent(
type: 'survey-completed-v1', // Уникальный тип события
name: 'Опрос пройден', // Человекочитаемое имя
properties: {
'surveyId': 'summer-2025-feedback',
'rating': '5',
},
);
GravitySDK.instance.triggerEvent(
context: context,
events: [customEvent],
pageContext: PageContext(
type: ContextType.other,
data: [],
location: 'app://survey/summer-2025-feedback',
),
);
Подробное описание всех доступных событий и их параметров — в разделе ниже.
#
Передача статуса Push-уведомлений
Чтобы платформа могла таргетировать кампании на пользователей в зависимости от статуса подписки на push-уведомления, необходимо передавать этот статус в SDK.
#
setNotificationPermissionStatus()
Устанавливает текущий статус разрешения на push-уведомления.
void setNotificationPermissionStatus(NotificationPermissionStatus status);
• status
: NotificationPermissionStatus.granted
, NotificationPermissionStatus.denied
или NotificationPermissionStatus.unknown
.
Пример получения и установки статуса (через firebase_messaging
):
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:gravity_sdk/gravity_sdk.dart';
// 1. Получаем статус из системы
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;
}
}
// 2. Передаем статус в SDK при старте приложения и после запроса разрешений
final status = await getNotificationStatus();
GravitySDK.instance.setNotificationPermissionStatus(status);
#
FAQ / Troubleshooting
Почему
GravityInlineWidget
не отображается или имеет нулевую высоту?- Вы не задали обязательный параметр
height
. Задайте высоту в конструктореGravityInlineWidget
.
- Вы не задали обязательный параметр
Почему UI кампании выглядит иначе, чем в превью?
- SDK наследует стили из
ThemeData
приложения. Если в кампании не задан шрифт/цвет, используетсяTheme.of(context)
.
- SDK наследует стили из
Почему клик по кнопке в кампании не приводит к переходу?
- Не обработаны события
FollowUrlEvent
/FollowDeeplinkEvent
вgravityEventCallback
. SDK не выполняет навигацию сам.
- Не обработаны события
#
Работа с контентом
#
GravityInlineWidget
(Автоматический рендеринг)
Виджет для встраивания кампаний (например, товарных рекомендаций) прямо в верстку вашего экрана.
GravityInlineWidget(
selector: 'homepage-recommendations',
height: 250, // Обязательно!
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://homepage',
),
)
#
ProductWidgetBuilder
(Кастомный рендеринг товаров)
Для полного контроля над внешним видом карточек товаров в GravityInlineWidget
, создайте свой класс, наследующий ProductWidgetBuilder
.
// 1. Создайте свой билдер
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? ?? ''),
),
);
}
}
// 2. Передайте его в initialize
await GravitySDK.instance.initialize(
// ...
productWidgetBuilder: CustomProductWidgetBuilder(),
);
⚠️ Обратите внимание: product.item представляет собой Map<String, dynamic>. Ключи в этой карте (например, 'name', 'price', 'imageUrl') соответствуют полям в вашем товарном фиде. Убедитесь, что вы используете правильные ключи и безопасно обрабатываете типы данных и возможные null значения.
#
JSON-кампании (Полностью ручной рендеринг)
Для максимальной гибкости вы можете получать кампании в виде чистого JSON и рендерить их самостоятельно.
⚠️ Важно: При работе с JSON-кампаниями SDK лишь доставляет данные. Ваше приложение несет полную ответственность за парсер JSON-объекта, полученного из
response.data
, и последующий рендеринг UI на основе этих данных.
#
getContentBySelector(...)
Запрашивает кампанию по селектору и возвращает ее данные.
Пример:
// Получаем контент для инлайнового блока на главной
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;
// ... ваша логика отображения
}
#
Событийная модель
Вы также можете получать JSON-кампании, которые активируются в ответ на события (trackView
, triggerEvent
). Для этого подпишитесь на gravityEventCallback
и отлавливайте событие ContentLoadEvent
.
await GravitySDK.instance.initialize(
// ... другие параметры
gravityEventCallback: (event) {
if (event is ContentLoadEvent) {
// Здесь ваша логика обработки и отображения данных из event.content
}
},
);
💡 Практическое руководство
Хотите увидеть полный пример реализации A/B-теста с UI, управляемым через JSON?
Ознакомьтесь с нашим пошаговым руководством: Гайд: A/B-тестирование в Flutter с использованием JSON. В нем мы на сквозном примере показываем, как связать getContentBySelector и sendContentEngagement для проведения полноценного эксперимента.
#
Трекинг взаимодействий (engagement)
Когда вы используете кастомный UI (ProductWidgetBuilder
или JSON-кампании), SDK не может автоматически отслеживать взаимодействия. Вы должны делать это вручную.
#
Зоны ответственности при использовании ProductWidgetBuilder
Когда вы используете ProductWidgetBuilder
для кастомного отображения товарных карточек, SDK и ваше приложение делят ответственность за отслеживание событий:
#
Что SDK делает автоматически:
- Показ виджета (
ContentImpressionEvent
): SDK автоматически фиксирует показ всего inline-блока, как только он загружается. - Видимость виджета (
ContentVisibleImpressionEvent
): SDK отслеживает, когда весь блок с рекомендациями становится видимым на экране пользователя (попадает во viewport). - Видимость карточки товара (
ProductImpressionEvent
): SDK автоматически отслеживает, когда конкретная карточка товара, отрисованная вашим билдером, становится видимой на экране.
#
Что должен реализовать разработчик:
- Клик по товару (
ProductClickEngagement
): SDK не может знать о внутренней структуре вашего кастомного виджета. Поэтому вы обязаны вызывать методGravitySDK.instance.sendProductEngagement()
внутри обработчика нажатия на вашу карточку товара. Без этого клики не будут засчитаны.
Пример реализации в ProductWidgetBuilder
:
onTap: () {
// 1. Отправляем событие клика в Gravity Field
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(product, content, campaign)
);
// 2. Выполняем навигацию на экран товара
// your_navigation_logic(product.item['url']);
},
#
sendContentEngagement()
Отправляет событие, относящееся ко всей кампании (например, показ баннера).
#
sendProductEngagement()
Отправляет событие, относящееся к конкретному продукту внутри кампании (например, клик по товару).
Пример:
// Отправка клика по товару
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(product, content, campaign),
);
// Отправка показа кастомного баннера (используйте VisibilityDetector)
GravitySDK.instance.sendContentEngagement(
ContentVisibleImpressionEngagement(content, campaign),
);
#
Справочник по событиям (TriggerEvent)
В таблице ниже описаны основные классы событий, которые можно отправлять с помощью triggerEvent(...)
.
#
Обработка обратных вызовов (Callbacks)
Подпишитесь на события SDK, передав функцию gravityEventCallback
в initialize
. Это критично для обработки навигации и запросов разрешений.
#
Справочник по событиям (TrackingEvent)
Пример обработки:
gravityEventCallback: (event) {
if (event is FollowUrlEvent) {
// Открыть URL с помощью url_launcher
} else if (event is FollowDeeplinkEvent) {
// Выполнить навигацию с помощью вашего роутера
} else if (event is RequestPushEvent) {
// Запросить разрешение на push-уведомления
}
}
Чтобы видеть клики/навигацию в отчетах, можно дополнительно отправлять CustomEvent
через triggerEvent(...)
в обработчиках колбэка.
#
FAQ (Часто задаваемые вопросы)
Q: В чем разница между trackView
и triggerEvent
?
A: Оба метода могут инициировать показ кампании. Используйте trackView
для отслеживания просмотров экранов (например, главная, карточка товара, корзина). Используйте triggerEvent
для отслеживания конкретных действий пользователя (например, добавление в корзину, покупка, логин).
Q: Почему мой GravityInlineWidget
не отображается или имеет нулевую высоту?
A: Самая частая причина — для GravityInlineWidget
не задан параметр height
. Flutter требует явных ограничений по размеру для многих виджетов. Убедитесь, что вы передали конкретное значение высоты: height: 250
.
Q: Как обрабатывать клики по кнопкам и ссылкам в In-App сообщениях (например, в модальном окне)?
A: SDK отправляет события FollowUrlEvent
(для внешних ссылок) и FollowDeeplinkEvent
(для внутренней навигации) в колбэк gravityEventCallback
, который вы передаете при инициализации. Вы должны реализовать логику обработки этих событий, например, используя пакет url_launcher
для открытия ссылок. SDK сам навигацию не выполняет.
Q: Что произойдет, если SDK не сможет получить кампанию от сервера (например, нет интернета)?
A: SDK обработает ошибку внутри. Если это GravityInlineWidget
, он просто не будет отображен (останется пустым). Если это In-App кампания, она не будет показана. Ошибки не должны приводить к падению приложения.
Q: Почему в ProductWidgetBuilder
я должен сам отправлять событие клика?
A: Потому что вы создаете полностью кастомный виджет. SDK не знает его внутреннюю структуру и на какой именно элемент пользователь должен нажать. SDK автоматически отслеживает только видимость вашей карточки, а за клик отвечает ваш код через вызов sendProductEngagement()
.
Q: Как мне протестировать кампанию перед запуском на всех пользователей?
A: Используйте таргетинг по CUID
(Custom User ID) в настройках кампании в интерфейсе Gravity Field. Укажите свой тестовый ID, и кампания будет показываться только вам.