Свой UI и Custom JSON в Android

Используйте этот путь, когда приложение само рисует кампанию или рекомендации. SDK получает контент и отправляет content load tracking; видимость, клики и навигацию определяет приложение. Сначала выполните инициализацию.

getContentBySelector(...)

Для getContentBySelector(...) требуется корректно заполненный PageContext.

Пример:

lifecycleScope.launch {
    val response = GravitySDK.instance.getContentBySelector(
        selector = JSONObject.quote("homepage-banner-json"),
        pageContext = PageContext(
            type = ContextType.HOMEPAGE,
            data = emptyList(),
            location = "app://home",
        )
    )

    if (response != null && response.data.isNotEmpty()) {
        val campaign = response.data.first()
        val variation = campaign.payload.firstOrNull()
        val content = variation?.contents?.firstOrNull()
        // Отрисуйте content в своем UI
    }
}

Метод возвращает null при сетевой ошибке или ошибке разбора ответа; успешный ответ без подходящей кампании содержит пустой data. Успешная загрузка автоматически отправляет content load tracking для всех contents, но не показывает UI.

Для выбора по ID и группе доступны getContentByCampaignId(...) и getContentByGroupSelector(...): сигнатуры.

Все три метода используют общие Options/ContentSettings и отправляют content load tracking.

Формат selector в Android 1.0.4

В опубликованном AAR и его исходниках selector, group и campaign ID обрабатываются через Json.parseToJsonElement(...). Поэтому строковое значение нужно передавать как JSON-строку. Для произвольного selector используйте org.json.JSONObject.quote(...):

val sdkSelector = JSONObject.quote("homepage-recommendations")
val response = GravitySDK.instance.getContentBySelector(
    selector = sdkSelector,
    pageContext = pageContext,
)

Та же особенность действует для GravityInlineCompose, GravityInlineView, GravityInlineListView и строкового campaignId. В XML JSON-кавычки задаются как ". Это обход текущей реализации: имя selector в кабинете остаётся обычной строкой без добавленных кавычек. Не кодируйте уже подготовленное значение повторно.

SDK также сериализует ContentSettings под ключом option внутри data, в то время как iOS использует options. Если интеграция зависит от fields или skusOnly, проверьте фактический запрос и результат на вашем backend; документация не гарантирует, что backend применит эти настройки в Android 1.0.4.

Какие параметры нужны для получения контента

Для вызова getContentBySelector(...) обязательны:

Опциональны:

  • lng;
  • pageNumber;
  • referrer;
  • utm;
  • attributes.

skusOnly и fields не передаются в метод напрямую. Они задаются один раз через setOptions(...) в ContentSettings и затем сериализуются в последующих запросах choose. Применение этих полей сервером в 1.0.4 проверьте с учётом ключа option, описанного выше.

Как устроен ответ ContentResponse

Упрощенно структура ответа выглядит так:

ContentResponse
  -> data: List<Campaign>
      -> payload: List<CampaignVariation>
          -> contents: List<CampaignContent>
              -> products?.slots: List<Slot>

Это важно для manual rendering и engagement:

  • campaign берите из response.data;
  • content берите из campaign.payload[*].contents[*];
  • slot берите из content.products?.slots[*].

decisionId присутствует в CampaignVariation, но публичные методы sendContentEngagement(...) и sendProductEngagement(...) не требуют передавать его отдельным параметром.

Custom JSON

Пользовательский JSON приходит в content.custom?.json как строка. Для кампании с JSON { "variant": "compact" } приложение само разбирает конфигурацию:

val campaign = response?.data?.firstOrNull()
val content = campaign?.payload?.firstOrNull()?.contents?.firstOrNull()
val config = content?.custom?.json?.let { raw ->
    runCatching { JSONObject(raw) }.getOrNull()
}
val variant = config?.optString("variant", "control") ?: "control"
// Покажите UI для variant; неизвестное значение обработайте своим fallback.

Вариант A/B выбирает сервер. Сохраняйте campaign/content для engagement; impression отправляйте после фактического показа выбранного варианта, бизнес-события — в момент действия.

Отдельного параметра headless у native SDK нет. Методы выбора контента сами не показывают UI, но trackView(...) и triggerEvent(...) могут запустить in-app кампанию. Для собственного UI используйте selector/Custom JSON кампании. Presentation lock не отменяет HTTP-запросы и content load tracking.

Аналитика собственного UI

Загрузка не означает показ. Сохраните исходные campaign/content и при работе с товарами slot. Передавайте их в engagement после фактического взаимодействия:

Действие Тип engagement
Контент появился в интерфейсе ContentImpressionEngagement
Контент достиг порога видимости ContentVisibleImpressionEngagement
Контент закрыт ContentCloseEngagement
Карточка товара достигла порога видимости ProductVisibleImpressionEngagement
Пользователь нажал на товар ProductClickEngagement

Порог и дедупликацию реализует приложение. Встроенный Android detector использует 50% площади в течение секунды; полный пример дополнительно дедуплицирует показ каждого размещения.

Публичного ContentClickEngagement нет. sendContentEngagement/sendProductEngagement вызывают tracking URL, но не gravityEventCallback. Для бизнес-действий отправляйте TriggerEvent отдельно. Справочник engagement.