#
Гайд: Headless-режим с Flutter SDK
Этот гайд предназначен для разработчиков, которые хотят использовать возможности персонализации Gravity Field, но полностью контролировать UI и UX в своём Flutter-приложении.
Headless-режим означает, что SDK выполняет только логику таргетинга и доставки данных, а отрисовка интерфейса и отправка событий взаимодействия (impressions, clicks) лежит на плечах приложения.
#
1. Концепции и поток данных
В Headless-режиме SDK выступает в роли "поставщика данных". Вы делаете запрос с контекстом, SDK возвращает JSON-пейлоад (например, цвет кнопки, текст баннера или список товаров), а ваше приложение рендерит это на экране.
Критически важно: Так как SDK не контролирует UI, оно не может автоматически отследить показ (Impression) или клик (Click). Вы обязаны вызывать методы трекинга вручную, иначе аналитика кампании будет пустой.
sequenceDiagram
participant App as App
participant SDK as SDK
participant Server as Server
Note over App: 1. Запрос контента
App->>SDK: trackViewNoShow / triggerEventNoShow
SDK->>Server: Request (Context + User ID)
Server-->>SDK: Response (JSON + DecisionID)
SDK-->>App: GravityDataResponse
Note over App: 2. Рендеринг UI
App->>App: Парсинг JSON и отображение виджетов
Note over App: 3. Обязательный трекинг
App->>SDK: sendContentEngagement(Impression)
SDK->>Server: WRIMP (Показ засчитан)
opt Пользователь нажал на элемент
App->>SDK: sendContentEngagement(Click)
SDK->>Server: CLICK (Клик засчитан)
end
#
2. Контракты данных
#
2.1. Запрос: PageContext
Корректный контекст — залог того, что кампания сработает. Параметры type и location должны совпадать с настройками таргетинга в дашборде.
Внимание
Если вы передадите ContextType.homepage, а кампания настроена на ContextType.cart, SDK не вернёт данные.
PageContext(
type: ContextType.product, // Тип страницы (обязательно)
data: ['sku-123'], // ID товара/категории (если применимо)
location: 'app://product/123', // Уникальный ID экрана
)
#
2.2. Ответ: GravityDataResponse
Когда SDK возвращает данные, вы получаете объект GravityDataResponse. Вот где искать ваши данные:
#
3. GravityContentCallback
GravityContentCallback — это тип функции, используемый для получения данных из Headless-методов. Поскольку сетевые запросы асинхронны, SDK использует этот колбэк для передачи GravityDataResponse обратно в ваше приложение, когда данные готовы.
Сигнатура:
typedef GravityContentCallback = void Function(GravityDataResponse response);
Как использовать:
Вы можете определить колбэк как метод в вашем классе или передать его как анонимную функцию.
// Определение обработчика
void onContentReceived(GravityDataResponse response) {
if (response.custom != null) {
// Обновляем состояние UI здесь
setState(() {
_bannerData = response.custom;
});
}
}
// Передача в SDK
GravitySDK.instance.trackViewNoShow(
context: context,
pageContext: pageContext,
onContent: onContentReceived, // Передаём ссылку на функцию
);
#
Best Practices
- State Management:
GravityContentCallbackвызывается вне цикла сборки (build cycle), поэтому обновления состояния (например,setState,bloc.add) должны обрабатываться аккуратно. - Обработка ошибок/пустых данных: Всегда проверяйте, содержит ли
response.customилиresponse.payloadвалидные данные перед попыткой отрисовки. - Безопасность контекста: Если вы обновляете UI внутри колбэка, убедитесь, что виджет всё ещё смонтирован.
#
4. Реализация
#
4.1. Получение данных
Есть три основных метода для получения headless-данных. Выберите тот, который подходит под ваш сценарий.
#
Вариант A: При просмотре экрана (trackViewNoShow)
Используйте, если кампания привязана к открытию экрана (например, баннер на главной).
// Define callback
GravityContentCallback onContent = (response) {
_handleHeadlessResponse(response);
};
await GravitySDK.instance.trackViewNoShow(
context: context,
pageContext: PageContext(
type: ContextType.homepage,
data: [],
location: 'app://home',
),
onContent: onContent,
);
#
Вариант B: При событии (triggerEventNoShow)
Используйте, если кампания триггерится действием (например, добавление в корзину).
await GravitySDK.instance.triggerEventNoShow(
context: context,
events: [AddToCartEvent(...)],
pageContext: PageContext(...),
onContent: _handleHeadlessResponse, // Pass method reference
);
#
Вариант C: Прямой запрос (getContentBySelectorWithDetails)
Используйте, если нужно загрузить конкретную кампанию по её API-селектору.
final response = await GravitySDK.instance.getContentBySelectorWithDetails(
selector: 'my_custom_banner',
pageContext: PageContext(...),
);
if (response != null) {
_handleHeadlessResponse(response);
}
#
4.2. Обработка ответа и парсинг JSON
Получив GravityDataResponse, извлеките данные и decisionId.
void _handleHeadlessResponse(GravityDataResponse response) {
// 1. Сохраняем decisionId для аналитики
final decisionId = response.decisionId;
// 2. Извлекаем Custom JSON
// Важно: response.custom может быть Map или String (в зависимости от версии SDK)
Map<String, dynamic> data;
if (response.custom is String) {
data = jsonDecode(response.custom);
} else {
data = Map<String, dynamic>.from(response.custom);
}
// 3. Передаём в UI (например, через State или BLoC)
setState(() {
_bannerData = BannerModel.fromJson(data);
_currentDecisionId = decisionId;
});
}
#
4.3. Обязательный трекинг (Engagement)
Это самый важный шаг. Если вы отобразили контент, но не отправили событие, кампания будет считаться "несработавшей".
#
Отправка Impression (Показ)
Вызывайте, когда виджет реально появился на экране (например, в initState или через VisibilityDetector).
if (_currentDecisionId != null) {
GravitySDK.instance.sendContentEngagement(
ContentImpressionEngagement.fromDecisionId(_currentDecisionId!)
);
}
#
Отправка Click (Клик)
Вызывайте в обработчике нажатия (например, onTap).
GestureDetector(
onTap: () {
if (_currentDecisionId != null) {
GravitySDK.instance.sendContentEngagement(
ContentClickEngagement.fromDecisionId(_currentDecisionId!)
);
}
// ... ваша логика перехода
},
child: ...
)
#
5. Best Practices & Architecture
#
5.1. Разделение ответственности
Не смешивайте логику SDK с UI-кодом. Используйте промежуточный слой (Repository или Service), который:
- Вызывает SDK.
- Парсит
GravityDataResponseв доменную модель. - Возвращает пару
(Model, DecisionId).
#
5.2. State Management
Headless-запросы асинхронны. Используйте BLoC, Riverpod или Provider для управления состояниями:
Loading: пока ждём ответ от SDK.Content: данные пришли, отображаем UI.Empty/Error: кампания не найдена или ошибка (показываем fallback или ничего).