#
SDK Gravity Field (Android)
Gravity Field SDK — это лёгкий клиент для интеграции мобильных приложений (Android) с платформой персонализации и A/B-тестирования. Он позволяет запускать кампании без необходимости реализовывать собственную логику таргетинга, выбора и аналитики.
SDK работает по принципу "тонкого клиента": все решения принимает сервер (бекенд Gravity Field), а SDK:
- передаёт контекст текущего экрана,
- получает кампании и их содержимое в виде JSON или встроенных шаблонов,
- активирует показ
inlineиin-appкампаний - трекает взаимодействия пользователя (просмотры, клики, покупки и т.д.),
- и помогает фиксировать конверсии для аналитики и обучения моделей.
📦 Эта документация предназначена для мобильных разработчиков и инженеров, которые интегрируют SDK в приложение.
Репозиторий Gravity Field SDK для Android
#
1. Установка
Добавьте модуль
gravity_sdkв ваш проект. Скопируйте модульgravity_sdkиз репозитория в корневую директорию вашего проекта.Подключите модуль в файле
settings.gradle.ktsвашего проекта:include(":gravity_sdk")Добавьте зависимость в файле
build.gradle.ktsвашего app-модуля:dependencies { implementation(project(":gravity_sdk")) }
#
2. Быстрый старт
Этот раздел проведет вас через основные шаги интеграции SDK, от инициализации до запуска вашей первой кампании.
#
Шаг 1: Инициализация SDK
Сначала необходимо инициализировать SDK с вашим apiKey и section. Это лучше всего делать в классе Application или в MainActivity. Критически важно подключить gravityEventCallback для обработки навигации и других действий.
import android.app.Application
import android.content.Intent
import android.net.Uri
import ai.gravityfield.gravity_sdk.GravitySDK
import ai.gravityfield.gravity_sdk.models.FollowDeeplinkEvent
import ai.gravityfield.gravity_sdk.models.FollowUrlEvent
import ai.gravityfield.gravity_sdk.models.TrackingEvent
import ai.gravityfield.gravity_sdk.models.UISettings
class App : Application() {
override fun onCreate() {
super.onCreate()
GravitySDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
section = "YOUR_SECTION_ID",
gravityEventCallback = ::handleGravityEvent,
uiSettings = UISettings(fontResId = R.font.your_custom_font) // Опционально: для кастомизации UI
)
}
private fun handleGravityEvent(event: TrackingEvent) {
println("Gravity SDK Event: ${event.javaClass.simpleName}")
when (event) {
is FollowUrlEvent -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(event.url))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
is FollowDeeplinkEvent -> {
// Здесь ваша логика навигации по диплинкам
println("Follow Deeplink: ${event.deeplink}")
}
else -> {
// Другие события
}
}
}
}
#
Почему важен gravityEventCallback?
SDK не выполняет навигацию самостоятельно. Когда пользователь нажимает на кнопку в кампании, SDK генерирует событие (например, FollowUrlEvent) и передает его в gravityEventCallback. Ваше приложение должно «поймать» это событие и выполнить соответствующее действие (открыть ссылку, перейти на другой экран). Без этого интерактивные элементы работать не будут.
Подробнее о событиях см. раздел
#
Шаг 2: Идентификация пользователя
Когда пользователь входит в систему, важно связать его действия, совершенные анонимно, с его постоянным профилем. Для этого после авторизации отправляется LoginEvent.
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
// ...
// Вызывается после успешной авторизации пользователя
fun onUserLoggedIn(context: Context, userCustomId: String) {
lifecycleScope.launch {
val loginEvent = LoginEvent(cuid = userCustomId, cuidType = "mySystemUserId")
GravitySDK.instance.triggerEvent(
events = listOf(loginEvent),
pageContext = PageContext(
type = ContextType.OTHER,
data = emptyList(),
location = "app://login"
),
activityContext = context
)
}
}
Отправка LoginEvent "склеивает" анонимный профиль с профилем авторизованного пользователя, сохраняя всю историю его действий.
Подробнее о различных способах идентификации читайте в разделе
#
Шаг 3: Отслеживание просмотров экранов
Gravity Field принимает решение о показе персонализированного контента на основе контекста страницы. Давайте отследим просмотр главной страницы.
// В коде вашей Activity или Fragment
fun trackHomepageView(context: Context) {
lifecycleScope.launch {
GravitySDK.instance.trackView(
pageContext = PageContext(
type = ContextType.HOMEPAGE,
data = emptyList(),
location = "app://homepage"
),
activityContext = context
)
}
}
Если для этого события настроена in-app кампания, SDK автоматически покажет ее.
#
Шаг 4: Отслеживание событий
Теперь отследим добавление товара в корзину. Это событие может запустить кампанию с товарными рекомендациями (например, "с этим товаром покупают").
fun trackAddToCart(context: Context, productId: String) {
lifecycleScope.launch {
val event = AddToCartEvent(
value = 99.99,
productId = productId,
quantity = 1,
currency = "RUB"
)
GravitySDK.instance.triggerEvent(
events = listOf(event),
pageContext = PageContext(
type = ContextType.PRODUCT,
data = listOf(productId),
location = "app://product/$productId"
),
activityContext = context
)
}
}
#
Шаг 5: Отображение inline-кампаний
Для отображения рекомендаций прямо в верстке страницы (например, блок "Персональные рекомендации") используйте GravityInlineView для XML-разметки или GravityInlineCompose для Jetpack Compose.
XML Layout:
<ai.gravityfield.gravity_sdk.ui.GravityInlineView
android:layout_width="match_parent"
android:layout_height="250dp"
app:selector="homepage-recommendations" />
Jetpack Compose:
import ai.gravityfield.gravity_sdk.ui.GravityInlineCompose
// ...
GravityInlineCompose(
modifier = Modifier.height(250.dp),
selector = "homepage-recommendations",
pageContext = PageContext(
type = ContextType.HOMEPAGE,
data = emptyList(),
location = "app://homepage"
)
)
Виджет сам загрузит и отобразит релевантный контент по селектору homepage-recommendations.
#
3. Инициализация и конфигурация
#
initialize()
Основной метод для настройки SDK. Вызывается один раз при старте приложения.
fun initialize(
context: Context,
apiKey: String,
section: String,
gravityEventCallback: GravityEventCallback,
productViewBuilder: ProductViewBuilder? = null,
productFilter: ProductFilter? = null,
uiSettings: UISettings? = null,
)
#
setOptions()
Позволяет задать глобальные настройки для всех последующих запросов.
fun setOptions(
options: Options?,
contentSettings: ContentSettings?,
proxyUrl: String?
)
options: Настройки для управления поведением запросов.contentSettings: Настройки для управления получаемым контентом.proxyUrl: URL прокси-сервера для отправки запросов.
Пример:
GravitySDK.instance.setOptions(
options = Options(
isReturnUserInfo = true // Возвращать информацию о пользователе в ответах
),
contentSettings = ContentSettings(
skusOnly = false, // Возвращать полную информацию о продуктах
fields = listOf("name", "price", "imageUrl") // Запросить конкретные поля
),
proxyUrl = "https://my-proxy.com"
)
#
4. Идентификация пользователя
SDK поддерживает два подхода к идентификации: автоматический (управляется SDK) и ручной (управляется вашим приложением).
#
Автоматическая идентификация
Это подход по умолчанию. При первом запросе SDK получает от сервера уникальный uid (user ID) и ses (session ID) и сохраняет их на устройстве. Все последующие запросы будут использовать эти идентификаторы.
Чтобы связать анонимный профиль с профилем авторизованного пользователя, после входа в систему отправьте LoginEvent.
// Вызывается после успешной авторизации
fun onUserLoggedIn(context: Context, userCustomId: String) {
lifecycleScope.launch {
val loginEvent = LoginEvent(
cuid = userCustomId,
cuidType = "mySystemUserId" // Укажите тип вашего идентификатора
)
GravitySDK.instance.triggerEvent(
events = listOf(loginEvent),
pageContext = PageContext(
type = ContextType.OTHER,
data = emptyList(),
location = "app://login"
),
activityContext = context
)
}
}
#
Ручная идентификация (setUser)
Если ваше приложение уже управляет ID пользователей и сессий, вы можете передавать их в SDK напрямую.
fun setUser(userId: String, sessionId: String)
Пример:
// Вызывается при старте сессии, если ID уже известны
GravitySDK.instance.setUser("user-from-my-system-42", "session-from-my-system-xyz")
В этом режиме SDK не будет использовать автоматически сгенерированный uid.
#
5. Передача контекста (trackView)
Отправляет событие просмотра экрана. В ответ может прийти кампания для показа.
suspend fun trackView(
pageContext: PageContext,
activityContext: Context
)
pageContext: Контекст страницы, который описывает, где находится пользователь.activityContext: КонтекстActivity, необходимый для отображения In-App кампаний.
Пример:
// На экране продукта
lifecycleScope.launch {
GravitySDK.instance.trackView(
pageContext = PageContext(
type = ContextType.PRODUCT,
data = listOf("product-sku-123"), // SKU продукта
location = "app://product/123"
),
activityContext = this@ProductActivity
)
}
#
PageContext
Ключевая модель для описания местоположения и контекста пользователя.
#
6. Трекинг событий (triggerEvent)
Отправляет одно или несколько событий о действиях пользователя. Может запустить кампанию.
suspend fun triggerEvent(
events: List<TriggerEvent>,
pageContext: PageContext,
activityContext: Context
)
events: Список событий для отправки.pageContext: Контекст страницы.activityContext: КонтекстActivity.
Подробное описание всех доступных событий — в разделе
#
7. Передача статуса Push-уведомлений
Чтобы платформа могла таргетировать кампании на пользователей в зависимости от статуса подписки на push-уведомления, необходимо передавать этот статус в SDK.
#
setNotificationPermissionStatus()
Устанавливает текущий статус разрешения на push-уведомления.
fun setNotificationPermissionStatus(status: NotificationPermissionStatus)
status:NotificationPermissionStatus.GRANTED,NotificationPermissionStatus.DENIEDилиNotificationPermissionStatus.UNKNOWN.
Пример получения и установки статуса:
import androidx.core.app.NotificationManagerCompat
// ...
val areNotificationsEnabled = NotificationManagerCompat.from(context).areNotificationsEnabled()
val status = if (areNotificationsEnabled) {
NotificationPermissionStatus.GRANTED
} else {
NotificationPermissionStatus.DENIED
}
GravitySDK.instance.setNotificationPermissionStatus(status)
#
8. Работа с контентом
#
In-App кампании (автоматический показ)
SDK автоматически отображает In-App кампании (модальные окна, bottom sheet, полноэкранные экраны, снэкбары), когда они приходят в ответ на вызовы trackView или triggerEvent.
#
Inline-кампании (встраиваемые)
#
XML Layouts: GravityInlineView
Используйте этот View для встраивания кампаний в XML-разметку.
<ai.gravityfield.gravity_sdk.ui.GravityInlineView
android:layout_width="match_parent"
android:layout_height="250dp"
app:selector="homepage-recs"
app:color="#FFF1F1F1"
app:cornerRadius="20dp" />
#
Jetpack Compose: GravityInlineCompose
Используйте эту Composable-функцию для встраивания кампаний в Jetpack Compose.
GravityInlineCompose(
modifier = Modifier.fillMaxWidth().height(250.dp),
selector = "homepage-recs",
pageContext = PageContext(
type = ContextType.HOMEPAGE,
data = emptyList(),
location = "app://homepage"
)
)
Виджет сам загрузит и отобразит релевантный контент по селектору homepage-recommendations.
#
Кастомный рендеринг товаров (ProductViewBuilder)
Для полного контроля над внешним видом карточек товаров в рекомендациях, вы можете реализовать интерфейс ProductViewBuilder. SDK предоставляет два способа интеграции: современный с использованием Jetpack Compose (рекомендуемый) и для Legacy Android Views.
⚠️ Важно: При использовании любого кастомного рендеринга вы обязаны самостоятельно реализовать отправку события клика по товару (
ProductClickEngagement). SDK автоматически отслеживает только показ (ProductVisibleImpressionEngagement) кастомных виджетов, но не может отследить клики внутри вашей верстки.
#
Реализация с Jetpack Compose (Рекомендуемый подход)
Создайте класс, реализующий интерфейс ProductViewBuilder, и в методе Build опишите вашу Composable-функцию. Для обработки нажатий используйте модификатор clickable.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import ai.gravityfield.gravity_sdk.GravitySDK
import ai.gravityfield.gravity_sdk.models.CampaignContent
import ai.gravityfield.gravity_sdk.models.ProductClickEngagement
import ai.gravityfield.gravity_sdk.models.Slot
import ai.gravityfield.gravity_sdk.network.Campaign
import ai.gravityfield.gravity_sdk.ui.product_view_builder.ProductViewBuilder
// 1. Создайте свой билдер
class MyComposeProductViewBuilder : ProductViewBuilder {
@Composable
override fun Build(slot: Slot, content: CampaignContent, campaign: Campaign) {
// Ваша кастомная верстка карточки товара
Box(
modifier = Modifier.clickable {
// 2. Отправляем событие клика по товару (PCLICK)
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign)
)
// 3. Здесь ваша логика навигации на экран товара
// yourNavigationLogic(slot.item["url"])
}
) {
Text(text = slot.item["name"] as? String ?: "Unknown Product")
}
}
}
// 3. Передайте экземпляр билдера в initialize
GravitySDK.initialize(
// ...
productViewBuilder = MyComposeProductViewBuilder(),
)
#
Реализация с Legacy Android Views
Если ваше приложение использует традиционную систему View, создайте класс, реализующий LegacyProductViewBuilder. В методе createView верните вашу кастомную View и установите на нее onClickListener.
import android.content.Context
import android.view.View
import android.widget.TextView
import ai.gravityfield.gravity_sdk.GravitySDK
import ai.gravityfield.gravity_sdk.models.CampaignContent
import ai.gravityfield.gravity_sdk.models.ProductClickEngagement
import ai.gravityfield.gravity_sdk.models.Slot
import ai.gravityfield.gravity_sdk.network.Campaign
import ai.gravityfield.gravity_sdk.ui.product_view_builder.LegacyProductViewBuilder
// 1. Создайте свой билдер
class MyLegacyProductViewBuilder : LegacyProductViewBuilder {
override fun createView(
context: Context,
slot: Slot,
content: CampaignContent,
campaign: Campaign
): View {
// Ваша кастомная верстка карточки товара
val productView = TextView(context).apply {
text = slot.item["name"] as? String ?: "Unknown Product"
// ... другие настройки View
}
// 2. Установите обработчик клика
productView.setOnClickListener {
// 3. Отправляем событие клика по товару (PCLICK)
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign)
)
// 4. Здесь ваша логика навигации на экран товара
// yourNavigationLogic(slot.item["url"])
}
return productView
}
}
// 4. Передайте экземпляр билдера в initialize
GravitySDK.initialize(
// ...
productViewBuilder = MyLegacyProductViewBuilder(),
)
#
JSON-кампании (ручной рендеринг)
Для максимальной гибкости вы можете получать кампании в виде чистого JSON и рендерить их самостоятельно.
#
getContentBy...()
Запрашивает кампанию по селектору или ID и возвращает ее данные.
suspend fun getContentBySelector(selector: String, pageContext: PageContext): ContentResponse
suspend fun getContentByCampaignId(campaignId: String, pageContext: PageContext): ContentResponse
Пример:
lifecycleScope.launch {
val response = GravitySDK.instance.getContentBySelector(
selector = "homepage-banner-json",
pageContext = PageContext(type = ContextType.HOMEPAGE, data = emptyList(), location = "app://home")
)
// ... ваша логика отображения на основе response.data
}
#
9. Кастомизация UI
SDK позволяет настраивать внешний вид отображаемых In-App кампаний, чтобы они соответствовали стилю вашего приложения.
#
Настройка шрифта
Вы можете передать собственный шрифт, который будет использоваться во всех текстовых элементах, отображаемых SDK (в модальных окнах, bottom sheet и т.д.).
Шаг 1: Добавьте файл шрифта в ресурсы
Поместите ваш файл шрифта (например, my_custom_font.ttf) в директорию res/font вашего Android-проекта.
Шаг 2: Передайте шрифт при инициализации SDK
При вызове GravitySDK.initialize используйте параметр uiSettings, чтобы указать ID вашего ресурса шрифта.
import android.app.Application
import ai.gravityfield.gravity_sdk.GravitySDK
import ai.gravityfield.gravity_sdk.models.UISettings
// ...
class App : Application() {
override fun onCreate() {
super.onCreate()
// Создаем объект настроек UI
val uiSettings = UISettings(fontResId = R.font.my_custom_font)
GravitySDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
section = "YOUR_SECTION_ID",
gravityEventCallback = ::handleGravityEvent,
uiSettings = uiSettings // Передаем настройки в SDK
)
}
// ...
}
После этого все текстовые элементы в UI-компонентах SDK будут использовать указанный вами шрифт.
#
10. Трекинг взаимодействий (Engagement)
Трекинг взаимодействий (engagement) в Android SDK организован на двух уровнях, чтобы обеспечить гибкий сбор аналитики как по виджетам в целом, так и по отдельным товарам внутри них. Понимание этой иерархии — ключ к правильной интеграции и достоверным отчетам.
#
Иерархия событий в кампании
Чтобы понять, какое событие за что отвечает, важно видеть их место в структуре данных кампании.
graph TD
subgraph Структура Кампании
A[Кампания / Виджет] --> B(content.events)
B --> C["WRIMP (Показ всего виджета)"]
B --> D["WCLICK (Клик по виджету/контенту)"]
A --> E[products]
E -- содержит --> F{products.slots}
subgraph Уровень Товара
F --> G{Slot 1}
F --> H{Slot 2}
F --> I[...]
G --> J(products.slots.events)
J --> K["PIMP (Показ товара)"]
J --> L["PCLICK (Клик по товару)"]
end
end
#
Диаграмма последовательности событий взаимодействия
sequenceDiagram
participant User as Пользователь
participant App as Приложение (UI)
participant SDK as Gravity SDK
participant Server as Gravity Field
User->>App: Скролл до виджета / экран показан
App->>SDK: Виджет стал видимым (>=50%)
SDK->>Server: WRIMP (ContentVisibleImpression)
SDK->>Server: PIMP для каждого видимого товара
User->>App: Клик по товару
App->>SDK: sendProductEngagement(ProductClickEngagement)
SDK->>Server: PCLICK
opt Дополнительно для A/B (опционально)
App->>SDK: sendContentEngagement(ContentClickEngagement)
SDK->>Server: WCLICK
end
Server-->>SDK: 200 OK
#
Назначение и использование событий
Каждый тип события служит для определенных аналитических целей.
#
События уровня виджета (WRIMP и WCLICK)
Эти события отслеживают взаимодействие со всей кампанией (виджетом).
- WRIMP (Widget Real Impression): Фиксирует показ всего виджета, когда он попадает в зону видимости пользователя. Это базовое событие для отслеживания видимости кампании.
- WCLICK (Widget Click): Используется для отслеживания кликов по не-рекомендательным виджетам, таким как баннеры, in-app, кнопки и т.д.
#
События уровня товара (PIMP и PCLICK)
Эти события необходимы для сбора статистики по рекомендательным кампаниям.
- PIMP (Product Impression): Фиксирует показ отдельного товара в рекомендательном блоке, когда он становится видимым.
- PCLICK (Product Click): Ключевое событие для аналитики рекомендаций. Оно содержит информацию о конкретном товаре и рекомендательной стратегии. Отправка PCLICK обязательна для того, чтобы в отчете по рекомендательным стратегиям появилась аналитика по кликам на товары.
💡 Продвинутый сценарий: WCLICK для A/B-тестов рекомендаций
В случае рекомендательных кампаний, разработчик может опционально отправлять событие WCLICK при клике на любую область виджета (включая клик по товару, одновременно с PCLICK).
Зачем это нужно? Это полезно для A/B-тестирования, когда требуется сравнить общий CTR (Click-Through Rate) разных рекомендательных алгоритмов. WCLICK позволяет измерить кликабельность всего виджета как единого целого, в то время как PCLICK измеряет интерес к конкретным товарам.
#
Функции SDK для ручной отправки событий
Когда SDK не может автоматически отследить взаимодействие (например, в кастомном UI), вы должны отправлять события вручную. Для этого в SDK предусмотрены два метода:
#
sendContentEngagement()
Используется для отслеживания взаимодействий с виджетом в целом (события WRIMP/WCLICK). По умолчанию SDK автоматически отправляет эти события для встроенных форматов. Это актуально, когда вы получаете кампанию через getContentBy...(), где вы сами отвечаете за рендеринг.
// Пример отправки события показа виджета
GravitySDK.instance.sendContentEngagement(
ContentVisibleImpressionEngagement(content, campaign)
)
Классы: ContentImpressionEngagement, ContentVisibleImpressionEngagement, ContentCloseEngagement.
#
sendProductEngagement()
Используется для отслеживания взаимодействий с отдельными товарами внутри виджета (события PIMP/PCLICK). Отправка PCLICK обязательна при использовании ProductViewBuilder. PIMP (показ) отправляется автоматически.
// Пример отправки события клика по товару
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign)
)
Классы: ProductClickEngagement, ProductVisibleImpressionEngagement.
#
Сценарий: Клик по товару в кастомном ProductViewBuilder
Если вы реализуете свой ProductViewBuilder, вы обязаны вручную отправлять событие PCLICK при нажатии на товар. SDK автоматически отслеживает только показ (PIMP) кастомных виджетов.
// Пример для Jetpack Compose
Box(
modifier = Modifier.clickable {
// 1. Отправляем событие клика по товару (PCLICK)
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign)
)
// 2. Выполняем навигацию на страницу товара
// yourNavigationLogic(slot.item["url"])
}
) {
// ... ваша верстка
}
// Пример для Legacy Android Views
productView.setOnClickListener {
// 1. Отправляем событие клика по товару (PCLICK)
GravitySDK.instance.sendProductEngagement(
ProductClickEngagement(slot, content, campaign)
)
// 2. Выполняем навигацию на страницу товара
// yourNavigationLogic(slot.item["url"])
}
#
11. Обработка обратных вызовов (Callbacks)
Подпишитесь на события SDK, передав функцию gravityEventCallback в initialize. Это критично для обработки навигации и запросов разрешений.
Пример обработки:
private fun handleGravityEvent(event: TrackingEvent) {
when (event) {
is FollowUrlEvent -> {
// Открыть URL
}
is FollowDeeplinkEvent -> {
// Выполнить навигацию по диплинку
}
is RequestPushEvent -> {
// Запросить разрешение на push-уведомления
}
// ... другие события
else -> {}
}
}
#
12. Справочник по событиям TriggerEvent
В таблице ниже описаны основные классы событий, которые можно отправлять с помощью triggerEvent(...).