Android SDK
Kixo Android SDK поддерживает Kotlin 2.0+ и Java, требует minSdk 24 (Android 7.0) и собран под compileSdk 35. За свой targetSdk по-прежнему отвечает само приложение. Один вызов Kixo.configure в вашем Application.onCreate автоматически отслеживает экраны, нажатия, сессии, сбои и события жизненного цикла. Для отслеживания push-уведомлений нужен мост FCM, описанный ниже. Автоматического отслеживания сетевых запросов в текущем Android-релизе нет. SDK также поддерживает повторы сессий, идентификацию пользователей и цели.
Быстрый старт
Нужно изменить три файла: добавить Maven-репозиторий, добавить зависимость и вставить две строки в свой подкласс Application.
Требования к сборке: compileSdk 35, minSdk 24, Kotlin 2.0+ или Java, а также bytecode Java 17.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://raw.githubusercontent.com/kixoio/kixo-android-sdk/main/repo")
}
}
}Примечание
На этом интеграция аналитики закончена. Стандартные автотрекеры включены по умолчанию; для push-уведомлений по-прежнему нужен мост FCM ниже. Переопределяйте отдельные флаги через KixoConfiguration.Builder(...) только при необходимости.
Добавьте в приложение
Maven-репозиторий Kixo размещён на GitHub Pages. Добавьте его рядом с google() и mavenCentral() в settings.gradle.kts:
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://raw.githubusercontent.com/kixoio/kixo-android-sdk/main/repo")
}
}
}Затем объявите зависимость в модуле приложения:
// app/build.gradle.kts
dependencies {
implementation("io.kixo:kixo-android-sdk:0.1.20")
}Совет
android.permission.INTERNET и android.permission.ACCESS_NETWORK_STATE уже включены в манифест SDK. Разрешениями на уведомления по-прежнему управляет ваше приложение; они объявляются только при подключении push-функций.
Многомодульные проекты
Конфигурация Gradle implementation — не транзитивна: если объявить implementation("io.kixo:kixo-android-sdk:0.1.20") в библиотечном модуле, например в :core_domain, зависимость Kixo НЕ станет доступна в :app или любом другом модуле-потребителе. Рабочих варианта два — выберите один.
Схема A — каждый модуль, который вызывает Kixo, объявляет зависимость сам (рекомендуется). Так classpath каждого модуля остаётся минимальным, а каскадных пересборок удаётся избежать. Используйте version catalog (libs.kixo.sdk), чтобы менять версию в одном месте.
// :core_domain/build.gradle.kts
dependencies {
implementation("io.kixo:kixo-android-sdk:0.1.20") // local use only
}
// :app/build.gradle.kts
dependencies {
implementation(project(":core_domain"))
implementation("io.kixo:kixo-android-sdk:0.1.20") // declared again — fine
}Схема B — реэкспорт через api(...). Объявление короче, но в публичный ABI библиотечного модуля попадают типы Kixo — любое обновление версии приведёт к пересборке всех downstream-модулей. Используйте этот вариант только если библиотека сама использует типы Kixo в своих публичных сигнатурах, например возвращает KixoDiagnostics из функции.
// :core_domain/build.gradle.kts
dependencies {
api("io.kixo:kixo-android-sdk:0.1.20") // re-exposed
}
// :app/build.gradle.kts
dependencies {
implementation(project(":core_domain")) // gets Kixo for free
}Предупреждение
Если при компиляции модуля появляется Unresolved reference: Kixo, значит, в этом модуле нет собственной зависимости на SDK — добавьте строку implementation выше или используйте вариант B.
Инициализация
Настраивайте Kixo в своём подклассе Application: onCreate выполняется раньше любой activity, поэтому просмотры экранов, нажатия и события жизненного цикла собираются с первого кадра. Зарегистрируйте Application в манифесте через android:name=".MyApp".
import android.app.Application
import io.kixo.sdk.Kixo
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Kixo.configure(
context = this,
projectId = "kx_proj_YOUR_PROJECT_ID",
apiKey = "kx_key_YOUR_API_KEY",
)
}
}Если нужна тонкая настройка — флаги автосбора, частота flush, сэмплирование replay, собственный хост API — создайте KixoConfiguration явно:
import io.kixo.sdk.Kixo
import io.kixo.sdk.KixoConfiguration
val config = KixoConfiguration.Builder(
projectId = "kx_proj_YOUR_PROJECT_ID",
apiKey = "kx_key_YOUR_API_KEY",
)
.autoTrackScreens(true)
.autoTrackTaps(true)
.autoTrackNetwork(false) // reserved; no-op in the current Android release
.autoTrackCrashes(true)
.autoTrackSessions(true)
.autoTrackPush(true)
.flushIntervalMillis(30_000)
.flushAt(20)
.maxBufferSize(200)
.build(applicationContext)
Kixo.configure(this, config)Примечание
Идемпотентно. Повторный вызов configure в рамках того же процесса ничего не делает и пишет WARN в лог — SDK сохраняет первую конфигурацию. События, которые ваш auth singleton ставит в очередь до того, как сработает до configure, буферизуются (до 50 штук) и отправляются после инициализации SDK, поэтому Kixo.identify(...) можно вызывать из глобального объекта до завершения Application.onCreate.
Отслеживание событий
Основная часть инструментирования строится на трёх примитивах: track для событий, markGoal для сигналов конверсии и addBreadcrumb для контекста вне событий.
import io.kixo.sdk.Kixo
Kixo.track("video_played", mapOf(
"video_id" to "vid_42",
"duration_ms" to 18_500,
"autoplay" to false,
))
// markGoal(name, value?, currency?, properties?) — pass extra context
// through the named 'properties' argument (a Map can't be the 2nd
// positional arg; that slot is the Double 'value').
Kixo.markGoal("activated", properties = mapOf(
"step" to "onboarding_completed",
))
// Revenue goals use the typed value + currency parameters:
Kixo.markGoal("purchase_completed", value = 49.99, currency = "USD")
Kixo.addBreadcrumb(
message = "user toggled dark mode",
category = "ui",
level = "info",
)Совет
У целей есть уровни важности. Отмеченные цели попадают в воронки активации Kixo и в ежедневный cron поиска изменений: если объём цели падает на 70% неделя к неделе, в дашборде она помечается бейджем Требует проверки. Используйте markGoal для нескольких действительно важных моментов, а track — для всего остального.
Стандартные события
Удобная обёртка над Kixo.track для событий, которые Kixo распознаёт по имени: это точные строковые ключи, с которыми работает детектор стандартных событий на бэкенде. Даёт проверку структуры свойств на этапе компиляции и единый источник истины для имён.
import io.kixo.sdk.Kixo
import io.kixo.sdk.SubscriptionInterval
Kixo.trackPurchase(
amount = 49.99,
currency = "USD",
productId = "pro_yearly",
)
Kixo.trackSubscriptionStart(
plan = "pro",
amount = 9.99,
currency = "USD",
interval = SubscriptionInterval.MONTH,
)
Kixo.trackSignup(method = "google")
Kixo.trackTrialStart(plan = "pro", days = 14)
Kixo.trackCancel(plan = "pro", reason = "too_expensive")
Kixo.trackUpgrade(fromPlan = "free", toPlan = "pro")
Kixo.trackActivation(event = "first_post_published")
Kixo.trackShare(channel = "twitter", contentId = "post_123")
Kixo.trackInvite(channel = "email", recipientCount = 5)Идентификация пользователей
Привяжите последующие события к постоянному user id и набору атрибутов. Склейка анонимного и известного пользователя происходит в Kixo: события, собранные до identify, задним числом будут отнесены к тому же пользователю.
import io.kixo.sdk.Kixo
// Reserved standard property keys carry a $-prefix (Mixpanel
// convention) so they namespace away from your own custom traits
// and promote to the dashboard's profile columns. See
// io.kixo.sdk.StandardProperty for the typed catalogue, or the
// "Standard property catalog" section below for the full 37-key list.
Kixo.identify("user_123", mapOf(
"$email" to "jane@example.com", // identity
"$name" to "Jane Doe", // identity
"$plan" to "pro", // subscription pack
"$lifetime_orders" to 12, // e-commerce pack
"signup_source" to "twitter_ad", // custom trait
))
// Logout: clear identity, super-properties, and the persisted queue.
Kixo.reset()⚠️ Ловушка со знаком доллара в Kotlin. Стандартные ключи идентификации начинаются с $ ($email, $name, $first_name), а в строковом литерале Kotlin знак доллара нужно экранировать как "\$email". Если написать "$email", Kotlin подставит значение переменной email, и оно незаметно сохранится как атрибут произвольный, а колонки Audience email / name так и останутся пустыми. Проще всего использовать типизированную перегрузку (SDK 0.1.13+): там ошибиться невозможно — Kixo.setUserProperty(StandardProperty.EMAIL, email).
Пометить пользователя для сегментации
Используйте setUserProperty со значением булево значение, чтобы добавить пользователю простой тег «да/нет». Тег сохраняется между запусками и используется в сегментах, email-кампаниях и запросах в чате — кроме вызова SDK ничего дополнительно настраивать не нужно.
// Tag a user as subscribed — segments + campaigns can target this
Kixo.setUserProperty("subscribe", true)
// VIP membership
Kixo.setUserProperty("vip", true)
// String + numeric values work too
Kixo.setUserProperty("plan_tier", "enterprise")
Kixo.setUserProperty("lifetime_orders", 42)
// Bulk-set
Kixo.setUserProperties(mapOf(
"subscribe" to true,
"plan_tier" to "enterprise",
))Свойства сохраняются через SharedPreferences между запусками и автоматически добавляются ко всем исходящим событиям. В чате можно написать, например, "отправить приветственное письмо пользователям, у которых subscribe = true", — Kixo сам соберёт сегмент и подготовит черновик шаблона. Очищаются при Kixo.reset().
Каталог стандартных свойств
Зарезервированные ключи свойств используют префикс $, чтобы не пересекаться с вашими пользовательскими traits. В каталоге Kixo — 37 ключей: 3 универсальных набора (identity, geo, lifecycle) и 5 отраслевых наборов для B2B (subscription, e-commerce, media, marketplace, loyalty). Задавайте только те, что подходят вашему продукту, — в дашборде будут показаны только заполненные наборы.
Идентификация
Используется всегда. Задаёт колонки в заголовке профиля.
| Ключ | Тип | Описание |
|---|---|---|
$email | строка | Основной email; часто используется как ключ для объединения профилей. |
$phone | строка | Номер телефона в формате E.164. |
$name | строка | Полное отображаемое имя. |
$first_name | строка | Имя. |
$last_name | строка | Фамилия. |
$avatar_url | строка | Полный URL аватара пользователя. |
Геоданные
Географический контекст.
| Ключ | Тип | Описание |
|---|---|---|
$country | строка | Код страны по ISO 3166. |
$city | строка | Название города. |
$region | строка | Штат или провинция. |
$timezone | строка | Часовой пояс IANA, например America/Los_Angeles. |
$language | строка | Тег IETF, например en или ru-RU. |
$locale | строка | Полный идентификатор локали. |
Жизненный цикл
Когда мы видели этого пользователя.
| Ключ | Тип | Описание |
|---|---|---|
$created | ISO8601 | Время регистрации или создания аккаунта. |
$last_seen | ISO8601 | Время последнего взаимодействия. |
Подписка
Используйте, если в вашем продукте есть тарифы.
| Ключ | Тип | Описание |
|---|---|---|
$plan | строка | Slug тарифа — free, pro, enterprise. |
$subscription_status | строка | active / trial / cancelled / past_due. |
$trial_ends | ISO8601 | Когда заканчивается текущий пробный период. |
$mrr | число | Ежемесячная регулярная выручка в валюте аккаунта. |
$subscription_started | ISO8601 | Когда началась текущая подписка. |
Электронная коммерция
Используйте, если продаёте товары.
| Ключ | Тип | Описание |
|---|---|---|
$lifetime_orders | число | Количество завершённых заказов. |
$lifetime_revenue | число | Общие расходы. |
$aov | число | Средний чек. |
$last_purchase | ISO8601 | Последняя успешная покупка. |
$first_purchase | ISO8601 | Первая успешная покупка. |
$cart_abandoned_count | число | Общее число брошенных корзин. |
Медиа
Используйте, если публикуете контент.
| Ключ | Тип | Описание |
|---|---|---|
$content_tier | строка | free / premium / paid. |
$subscribed_categories | CSV-строка или массив | Категории, на которые подписан пользователь. |
$watch_time_total | число | Общее время просмотра в секундах. |
$last_played | ISO8601 | Время последнего начала воспроизведения. |
Маркетплейс
Используйте, если ваш продукт — двусторонняя платформа.
| Ключ | Тип | Описание |
|---|---|---|
$seller_tier | строка | Slug уровня на стороне продавца. |
$buyer_tier | строка | Slug уровня на стороне покупателя. |
$listings_count | число | Активные объявления пользователя. |
$reviews_count | число | Отзывы, полученные пользователем. |
$verified | булево значение | Статус KYC. |
Лояльность
Используйте для программ вовлечения и вознаграждений.
| Ключ | Тип | Описание |
|---|---|---|
$loyalty_points | число | Текущий доступный баланс баллов. |
$vip_level | строка | Slug уровня VIP. |
$referral_count | число | Успешные рефералы, засчитанные этому пользователю. |
Совет
Не нашли подходящий вариант? Используйте обычные ключи для пользовательских traits. Они появятся в панели Custom Traits в дашборде и не будут засорять колонки профиля. Пять отраслевых наборов выше — это практичные заготовки для самых распространённых моделей B2B; терминология конкретного продукта, например shipping_plan, остаётся без префикса.
Super-properties
Пары ключ-значение на уровне сессии, которые автоматически добавляются ко всем исходящим событиям. В отличие от traits в identify, которые описывают личность пользователя, super-properties описывают контекст сессии: активный вариант A/B-теста, flavor сборки, включённые feature flag. Сохраняются между запусками и очищаются при reset(). Если ключи совпадают, приоритет всегда у properties в событии, отправленном через track.
import io.kixo.sdk.Kixo
Kixo.setSuperProperty("build_flavor", "beta")
Kixo.setSuperProperties(mapOf(
"ab_variant" to "B",
"referrer_campaign" to "autumn-launch",
))
// Sugar for A/B tracking — stored as 'experiment_<id>'.
Kixo.setExperimentVariant("checkout_v2", "variant_a")
Kixo.unsetSuperProperty("build_flavor")
Kixo.clearSuperProperties()Push-уведомления
Есть два пути интеграции. Выберите A, если вы используете FCM и хотите как можно быстрее получить рабочую настройку. Выберите B, если у вас уже есть собственный FirebaseMessagingService, который нельзя перестроить, или если нужен явный контроль над тем, какие доставки FCM видит Kixo.
Вариант A — унаследоваться от KixoFirebaseMessagingService (автоотслеживание)
Унаследуйтесь от KixoFirebaseMessagingService и вызовите super.onMessageReceived(...) в своём override — Kixo автоматически отправит push_received для видимого уведомления или push_silent для data-only сообщения. Базовый класс также обрабатывает регистрацию onNewToken, если вы её не переопределяете. Регистрация AndroidManifest.xml не отличается от обычного сервиса FCM.
import com.google.firebase.messaging.RemoteMessage
import io.kixo.sdk.KixoFirebaseMessagingService
class MyMessagingService : KixoFirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage) // Kixo auto-tracks push_received
// … your own routing / notification display
}
}Примечание
Kixo компилирует этот необязательный класс с Firebase Messaging, но не подтягивает Firebase в приложение транзитивно. В SDK Firebase объявлен как compileOnly; если приложение выбирает этот вариант, зависимость от firebase-messaging у него уже должна быть — как и у любого FCM receiver.
Вариант B — вызывать API вручную из собственного FCM service
Зарегистрируйте FCM token в Kixo через FirebaseMessagingService.onNewToken, а затем явно логируйте каждую доставку. Этот вариант подходит, если Kixo должен видеть только часть доставок FCM. Сейчас на Android поддерживается доставка только через FCM.
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import io.kixo.sdk.Kixo
import io.kixo.sdk.PushProvider
class MyMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
Kixo.setPushToken(token, PushProvider.FCM)
}
override fun onMessageReceived(message: RemoteMessage) {
// Convert the FCM payload to a Map<String, Any?> and log it —
// Kixo correlates this with the open / dismiss it sees later.
Kixo.logPushReceived(message.data.toMap(), appState = "background")
}
}Android не даёт универсального lifecycle-хука для открытий уведомлений, их закрытия и нажатий на кнопки действий. Передавайте эти сигналы из notification intent или receiver, которые создаёт ваше приложение:
import io.kixo.sdk.Kixo
Kixo.logPushOpened(payload = pushPayload) // open
Kixo.logPushOpened(payload = pushPayload, actionId = "reply") // action-button tap
Kixo.logPushDismissed(payload = pushPayload) // swipe-awayЗапись сессий
Повторяйте реальную визуальную картину того, что видел пользователь. При каждом захвате SDK кодирует сжатый кадр экрана (изображение JPEG) вместе со структурным снимком иерархии представлений и загружает оба артефакта — благодаря этому проигрыватель в дашборде может показывать попиксельно точное воспроизведение рядом с временной шкалой взаимодействий. Настройте replay для проекта в Панель управления → Настройки → Повтор сеанса. SDK автоматически получает и обновляет эту проектную политику, включая маскирование, режимы захвата и разрешение на загрузку по сотовой сети.
import io.kixo.sdk.Kixo
import io.kixo.sdk.KixoConfiguration
val config = KixoConfiguration.Builder(
projectId = "kx_proj_YOUR_PROJECT_ID",
apiKey = "kx_key_YOUR_API_KEY",
)
.build(applicationContext)
Kixo.configure(this, config)Если загрузка по сотовой сети отключена, поставленные в очередь записи replay ждут подходящей сети.
Совет
Маскирование до отправки. Kixo записывает пиксели, поэтому маскирование выполняется до до того, как какие-либо данные покинут устройство. Поля пароля и e-mail определяются автоматически и скрываются; текст в структурных снимках проходит через фильтр PII; а любое представление, помеченное setKixoMask(true), превращается в непрозрачный прямоугольник в кадре до до кодирования в JPEG — его пиксели никогда не покидают устройство. Экраны Jetpack Compose по умолчанию маскируются целиком; вызовите setKixoMask(false) на внешнем ComposeView, чтобы включить экран, который вы уже проверили. В дашборде оператор видит повтор сессии рядом с временной шкалой событий.
Сбор данных
SDK собирает данные, включённые в проекте, а также события и свойства, которые отправляет ваше приложение.
Отладка
Kixo.diagnostics() возвращает снимок состояния SDK только для чтения — удобно для скрытого экрана отладки или smoke-теста. Помогает понять, почему события не уходят, без отладчика.
import io.kixo.sdk.Kixo
val diag = Kixo.diagnostics()
Log.d("Kixo", "queued=${diag.queue.bufferedEventCount}")
Log.d("Kixo", "paused=${diag.paused}") // collection paused state
Log.d("Kixo", "lifecycleState=${diag.lifecycleState}") // SDK lifecycle stateЧтобы принудительно выполнить flush из тестового окружения, вызовите его явно — метод может блокироваться на сетевом обмене до timeoutMs:
import io.kixo.sdk.Kixo
// Async fire-and-forget — returns immediately.
Kixo.flush()
// Blocking variant for instrumentation tests. Never call on the main thread.
val landed: Boolean = Kixo.flushBlocking(timeoutMs = 5_000L)
assertTrue(landed)Compose Navigation
Маршруты Activity / Fragment сразу создают события screen_view и структурированные записи screen_visit с метаданными о времени на экране и переходах. Для Jetpack Compose Navigation вызывайте Kixo.screen из LaunchedEffect, привязанного к маршруту, — тогда SDK увидит одно событие на каждый destination, независимо от числа рекомпозиций.
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import io.kixo.sdk.Kixo
@Composable
fun AppNavHost(nav: NavHostController) {
NavHost(navController = nav, startDestination = "home") {
composable("home") {
LaunchedEffect("home") { Kixo.screen("HomeScreen") }
HomeScreen()
}
composable("settings") {
LaunchedEffect("settings") { Kixo.screen("SettingsScreen") }
SettingsScreen()
}
}
}AI-агенты для разработки
Публичный API SDK небольшой и хорошо приспособлен для автодополнения: все методы доступны на синглтоне Kixo, каждый пример Kotlin в этом руководстве начинается с import io.kixo.sdk.Kixo, а в нашем README есть блок "AI agent quick reference", который такие инструменты, как Claude Code, Cursor и Codex, могут сразу вставить в свой контекст. Если ваш агент зашёл в тупик, начните с этого канонического примера:
// Tell your AI coding agent:
// "Integrate the Kixo Android SDK using io.kixo:kixo-android-sdk
// from https://raw.githubusercontent.com/kixoio/kixo-android-sdk/main/repo.
// Call Kixo.configure(this, projectId, apiKey) in Application.onCreate.
// Then use Kixo.track / Kixo.identify / Kixo.markGoal as needed."Примечание
Все разделы выше написаны с расчётом на этот сценарий: импорты всегда указаны явно, типы всегда названы, а синглтон SDK нигде не скрыт за псевдонимом. Передайте эту страницу своему агенту — дальше он доведёт интеграцию сам.