Saltar al contenido principal

Integración del SDK de Android

SDK nativo de Android para rastrear eventos, gestionar sesiones de usuario, enviar notificaciones push mediante FCM y mostrar mensajes in-app.

Funciones

  • Ligero: ocupa poco espacio.
  • Rápido: optimizado para el rendimiento.
  • Compatibilidad sin conexión: cola de eventos basada en SQLite.
  • Reintento automático: espera exponencial ante fallos.
  • Procesamiento por lotes: agrupación eficiente de eventos (50 eventos / 5 s).
  • Mensajería in-app: mensajes nativos y HTML, 5 tipos, con límite de frecuencia.
  • Notificaciones push: Firebase Cloud Messaging (FCM).
  • Privacidad primero: cumple el RGPD y respeta el consentimiento de los usuarios.
  • Android 6.0+: compatible con API 23+.

Requisitos

  • Android 6.0 (nivel de API 23) o superior: el mínimo para el almacenamiento cifrado en reposo (EncryptedSharedPreferences) y el mismo que exige Firebase Cloud Messaging.
  • Kotlin 1.9.20 o superior.
  • Gradle 8.0 o superior.

Configuración local del SDK

Para compilaciones locales, define la ruta del SDK de Android en local.properties:

sdk.dir=/Users/your-user/Library/Android/sdk

También puedes exportar ANDROID_HOME/ANDROID_SDK_ROOT antes de ejecutar Gradle.

Instalación

Gradle (recomendado)

Añade esto a build.gradle.kts:

El SDK se distribuye en dos artefactos. Añade uno de ellos: el artefacto de UI contiene el base, así que nunca declaras los dos:

dependencies {
// Todo, incluida la visualización de mensajes in-app. Empieza aquí.
implementation("io.joryio:joryio-android-ui:1.0.0")
}
dependencies {
// Solo tracking, identidad y push: sin renderizado in-app y sin WebView
// enlazado. Elige esto si tu app no usa mensajes in-app, o si los
// renderizas tú mismo.
implementation("io.joryio:joryio-android:1.0.0")
}

O con Groovy (build.gradle):

dependencies {
implementation 'io.joryio:joryio-android-ui:1.0.0'
}
¿Cuál elegir?

joryio-android-ui depende de joryio-android, así que una línea te da ambos y no pueden desincronizarse: solo nombras una versión.

Usa el artefacto base solo cuando tu app no necesite mensajes in-app, o cuando una revisión de seguridad se oponga a que haya un WebView enlazable. Los mensajes in-app simplemente no se mostrarán; todo lo demás funciona igual.

Inicio rápido

1. Inicializa el SDK

En tu clase Application:

import io.joryio.sdk.Joryio
import io.joryio.sdk.JoryioConfig

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

// Inicializa con tu clave de SDK
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_SDK_KEY",
apiHost = "api-eu1.joryio.com"
)
}
}
Encontrar tu clave de SDK

Encuentra tu clave de SDK en el dashboard de Joryio: Configuración → Apps → [Tu app] → Claves de SDK.

2. Rastrear eventos

// Evento básico
Joryio.track("Button Tapped")

// Evento con propiedades
Joryio.track("Product Viewed", mapOf(
"product_id" to "abc123",
"product_name" to "Wireless Headphones",
"price" to 99.99,
"category" to "Electronics"
))

// Vista de pantalla
Joryio.trackScreen("ProductDetail", mapOf(
"product_id" to "abc123"
))

3. Identificar usuarios

// Identifica un usuario
Joryio.identify("user-123")
Joryio.getInstance().setAttributes(mapOf(
"email" to "user@example.com",
"name" to "John Doe",
"plan" to "premium"
))

// Establece atributos más adelante
Joryio.setAttribute("last_purchase", Date())
Joryio.incrementAttribute("lifetime_value", 99.99)

// Al cerrar sesión
Joryio.reset()

Datos automáticos de sesión

El SDK de Android enriquece los eventos Session Start con datos de dispositivo y entorno:

  • $device_id
  • $platform (android)
  • $manufacturer / $model
  • $os_name / $os_version / $os_sdk_int
  • $app_version / $build_number
  • $package_name
  • $screen_width / $screen_height
  • $locale
  • $language / $languages
  • $timezone
  • country (ISO-3166-1 alfa-2, derivado de la IP al iniciar la sesión)

Notificaciones push

Activa las notificaciones push con Firebase Cloud Messaging (FCM).

1. Añadir Firebase a tu proyecto

Sigue la guía de configuración de Firebase para añadir Firebase a tu proyecto de Android.

2. Añadir el servicio a AndroidManifest.xml

<service
android:name="io.joryio.sdk.push.JoryioFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

3. Registrar notificaciones push

import com.google.firebase.messaging.FirebaseMessaging

// Obtén el token de FCM y regístralo
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
Joryio.getInstance().registerPushToken(token)
}
}

// Comprueba si push está activado
val isEnabled = Joryio.getInstance().isPushEnabled()

// Anula el registro cuando sea necesario
Joryio.getInstance().unregisterPush()

4. Configurar FCM en el dashboard

Para enviar notificaciones push, configura tus credenciales de Firebase:

  1. Ve a Configuración → Apps → [Tu app].
  2. Abre la configuración de notificaciones push.
  3. Sube el JSON de cuenta de servicio de Firebase (desde Firebase Console: Configuración del proyecto → Cuentas de servicio → Generar clave privada nueva).
  4. Guarda la configuración.

Mensajería in-app

El SDK muestra los mensajes in-app por ti. Una vez inicializado, las campañas elegibles aparecen solas y las impresiones, los clics y los descartes se registran automáticamente: no hay nada que conectar.

Tokens de entrega (delivery tokens)

Cuando el backend sirve una campaña elegible, emite también un token de entrega firmado y de corta duración. El SDK lo devuelve al informar una impresión, un clic o un cierre, y el servidor verifica la firma antes de registrar nada.

No tienes que hacer nada: el SDK se encarga por ti. Se documenta porque cambia lo que ocurre con un cliente que no envía token:

POST /v1/in-app/track   (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }

El token es lo que hace que una impresión sea fiable: sin él, cualquiera que tenga la SDK key - que viaja dentro de cada app y cada página - podría informar impresiones y clics de una campaña que nunca se mostró, y tus informes los contarían.

Si dejan de registrarse impresiones in-app, comprueba que la app esté compilada contra una versión actual del SDK: una compilación anterior a los tokens de entrega no envía token y el servidor rechazará sus impresiones.

Dos tipos de contenido

Cada campaña llega en una de dos formas de contenido, y el SDK renderiza cada una de manera distinta:

ContenidoQué esCómo se renderiza
NativoDatos estructurados: titular, texto, imagen, botonesVistas Android reales, con el tema, las fuentes, el modo oscuro y TalkBack de tu app. Sin WebView.
HTMLMarcado, CSS y JavaScript escritos por el autorUn WebView dentro del mensaje.

Permitir mensajes HTML

Los mensajes HTML están desactivados por defecto. Un mensaje HTML ejecuta JavaScript escrito por el autor dentro de tu app, así que activarlo es una decisión del equipo de la app, no algo que se enciende desde una plataforma de marketing:

val config = JoryioConfig(
allowHtmlJsInAppMessages = true // por defecto: false
)

Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com",
config = config
)

Dejarlo desactivado no desactiva la mensajería in-app. Los mensajes nativos siguen mostrándose, porque son datos que tu app dibuja con sus propias vistas, sin ningún intérprete. Las campañas HTML se omiten y se registran, de modo que una app que no lo ha activado no muestra nada en lugar de un mensaje roto.

Si tu política de seguridad prohíbe ejecutar HTML de terceros en el proceso, déjalo desactivado y crea tus campañas como mensajes nativos.

Encargarte tú del renderizado

Define un callback para dibujar tu propia interfaz. Sustituye al renderizado del SDK, así que no recibirás dos copias del mensaje:

Joryio.getInstance().setInAppMessageCallback { campaign ->
showInAppMessage(campaign) // tu interfaz
}

// Informa de lo ocurrido: el SDK solo registra automáticamente lo que muestra él
Joryio.getInstance().trackInAppImpression(campaignId, "viewed")
Joryio.getInstance().trackInAppImpression(campaignId, "clicked")
Joryio.getInstance().trackInAppImpression(campaignId, "dismissed")

Renderizar los mensajes tú mismo

El SDK dibuja los mensajes nativos, pero puedes tomar el control por completo: el equivalente a un custom view factory.

Joryio.getInstance().setInAppMessageCallback { campaign ->
// dibújalo como quieras
}

Definir un callback sustituye al renderizador integrado en vez de ejecutarse junto a él, así que el mensaje aparece una vez, no dos.

campaign.content ya viene resuelto: el Liquid se renderiza en el servidor y los campos nativos llegan como texto plano (no los pases a un WebView; no llevan escape HTML precisamente porque son para vistas de texto). Informa de lo que muestres con trackInAppImpression(campaignId, "impression" | "clicked" | "dismissed").

Tipos de mensaje

El SDK admite 5 tipos de mensaje:

  1. Modal - Centro de la pantalla con fondo atenuado
  2. Banner - Parte superior de la pantalla
  3. Slide-Up - Notificación pequeña desde abajo
  4. Full-Screen - Mensaje a pantalla completa
  5. Custom - Tu app decide la ubicación

Opciones de configuración

Personaliza el comportamiento del SDK con JoryioConfig:

val config = JoryioConfig(
// ID de usuario inicial (opcional)
userId = "user-123",

// Procesamiento de eventos por lotes
batchSize = 50, // Eventos por lote
flushInterval = 5000, // Intervalo de vaciado en ms (5 s)

// Gestión de sesión
sessionTimeout = 1800000, // Tiempo de espera de sesión en ms (30 min)
trackSessionStart = true, // Rastrea automáticamente el inicio de sesión

// Reintentos de red
maxRetries = 3, // Máximo de intentos de reintento

// Controles de privacidad
optOut = false, // Exclusión del rastreo
trackingConsent = TrackingConsent.GRANTED,

// Depuración
enableDebug = false, // Activa el registro de depuración
logLevel = LogLevel.ERROR // Nivel de registro
)

Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com",
config = config
)

Consentimiento de rastreo

enum class TrackingConsent {
GRANTED, // Se permite el rastreo completo
PENDING, // A la espera de la decisión del usuario
DENIED // El usuario rechazó el rastreo
}

Niveles de registro

enum class LogLevel {
VERBOSE, // Todos los registros
DEBUG, // Depuración y superiores
INFO, // Información y superiores
WARN, // Advertencias y errores
ERROR // Solo errores
}

Funciones avanzadas

Gestión de sesión

Las sesiones rastrean automáticamente la interacción del usuario:

// Las sesiones se gestionan automáticamente con un tiempo de espera de 30 minutos
// Obtén el ID de sesión actual
val sessionId = Joryio.getInstance().getSessionId()

// Las sesiones se actualizan con la actividad del usuario

Atributos de usuario

// Establece varios atributos
Joryio.getInstance().setAttributes(mapOf(
"age" to 28,
"city" to "San Francisco",
"premium" to true
))

// Establece un atributo
Joryio.setAttribute("language", "en")

// Incrementa un atributo numérico
Joryio.incrementAttribute("page_views", 1)
Joryio.incrementAttribute("total_spent", 29.99)

// Elimina un atributo
Joryio.getInstance().unsetAttribute("temporary_flag")

Controles de privacidad

// Stop collecting. PERSISTED - survives an app restart.
Joryio.getInstance().optOut()

// Opt back in
Joryio.getInstance().optIn()

// Check opt-out status
if Joryio.getInstance().isUserOptedOut() {
print("User has opted out")
}

// Delete everything the SDK stored on this device.
// SEPARATE from optOut(): "stop collecting" and "delete what you have" are
// different requests. This is the one an erasure request needs. It does NOT
// opt the user out - call optOut() as well if that is also intended.
Joryio.getInstance().wipeData()

// Get identity info
val (userId, anonymousId) = Joryio.getInstance().getIdentity()
println("User: ${userId ?: "anonymous"}, Anonymous ID: $anonymousId")

What optOut() does, precisely:

stops collectionyes - track, identify and setAttributes all become no-ops
survives a restartyes - the flag is stored on the device and read before anything is collected
drops what is already queuedyes - queued events and un-acked attribute writes are discarded, not delivered later
tells the serveryes - one final $tracking_opted_out profile attribute, best-effort, sent while sending is still permitted
deletes stored datano - use wipeData()

The $tracking_opted_out attribute is a record, not enforcement: it lands on the profile so campaigns can exclude on it. Server-side suppression is a separate setting.

nota

Actualizado en inglés - traducción pendiente.

Vaciado manual de la cola

// Vacía los eventos de inmediato
Joryio.flush()

// Útil antes de terminar la aplicación
override fun onDestroy() {
super.onDestroy()
Joryio.flush()
}

Prácticas recomendadas

1. Inicializar pronto

Inicializa en tu clase Application:

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Joryio.initialize(
context = this,
sdkKey = "jry_sdk_android_YOUR_KEY",
apiHost = "api-eu1.joryio.com"
)
}
}

2. Rastrear vistas de pantalla

Usa el rastreo de pantallas para la navegación:

override fun onResume() {
super.onResume()
Joryio.trackScreen(this::class.simpleName ?: "Unknown")
}

3. Gestionar el cierre de sesión del usuario

Restablece siempre al cerrar sesión:

fun logout() {
// Borra la sesión de usuario
clearUserSession()

// Restablece el SDK
Joryio.reset()
}

Referencia de API

Rastreo de eventos

// Rastrea un evento
Joryio.track(
eventName: String,
properties: Map<String, Any?> = emptyMap()
)

// Rastrea una vista de pantalla
Joryio.trackScreen(
screenName: String,
properties: Map<String, Any?> = emptyMap()
)

// Vacía los eventos de inmediato
Joryio.flush()

Identidad de usuario

// Identifica un usuario
Joryio.identify(
userId: String
)

// Establece atributos
Joryio.getInstance().setAttributes(
attributes: UserAttributes
)

// Asigna un alias al usuario
Joryio.alias(userId: String)

// Restablece el usuario (cierre de sesión)
Joryio.reset()

// Obtén los ID
Joryio.getInstance().getAnonymousId(): String
Joryio.getInstance().getUserId(): String?
Joryio.getInstance().getSessionId(): String

Mensajería in-app

// Define el callback de mensajes
Joryio.getInstance().setInAppMessageCallback { campaign ->
// Gestiona la visualización del mensaje
}

// Rastrea impresiones
Joryio.getInstance().trackInAppImpression(
campaignId: String,
action: String
)

Notificaciones push

// Registra el token
Joryio.getInstance().registerPushToken(token: String)

// Comprueba el estado
Joryio.getInstance().isPushEnabled(): Boolean

// Anula el registro
Joryio.getInstance().unregisterPush()

Solución de problemas

Los eventos no aparecen

  1. Comprueba que la clave de SDK sea correcta: jry_sdk_android_*.
  2. Activa el registro de depuración: enableDebug = true.
  3. Comprueba logcat en busca de errores.
  4. Verifica los permisos de red en el manifest.
  5. Llama a flush() para enviar de inmediato.

Push no funciona

  1. Verifica que Firebase esté configurado correctamente.
  2. Comprueba que el JSON de cuenta de servicio de Firebase esté cargado en el dashboard.
  3. Asegúrate de que el token de dispositivo esté registrado.
  4. Prueba primero con Firebase Console.

Errores de compilación

  1. Asegúrate de que la versión mínima de SDK sea 23.
  2. Sincroniza las dependencias de Gradle.
  3. Limpia y recompila el proyecto.

Rastreo de e-commerce

El SDK de Android incluye un rastreador de e-commerce integrado para eventos de producto, carrito, checkout y pedido. Consulta la guía de rastreo de e-commerce común a todos los SDK para ver la API completa con ejemplos en Kotlin.

Siguientes pasos

APIs de prueba y diagnóstico

Dos grupos, y la diferencia importa.

APIs de prueba: se ignoran salvo que enableDebug esté activo. Cambian estado real, así que una llamada perdida en una compilación de producción corrompería límites de frecuencia e informes reales.

MétodoQué hace
resetDisplayedCampaigns()Olvida qué campañas in-app ya se mostraron. El estado de frecuencia vive en el dispositivo, así que cambiar la campaña en el servidor NO hará que se muestre de nuevo.
evaluateInAppCampaigns()Repite la decisión de visualización sobre las campañas ya sincronizadas, sin red.

APIs de diagnóstico: siempre disponibles, también en producción. Solo leen estado, así que no pueden dañar nada.

MétodoResponde a
getQueueSize()¿Cuántos eventos esperan envío?
currentApiEndpoint / lastTransportError¿Con qué servidor hablamos y falló la última llamada?

getIdentity(), getSessionInfo() y getDeviceToken() NO pertenecen a ninguno de los dos grupos: son API normal. Leer la identidad del dispositivo es algo habitual en una app.

Attribute delivery

setAttributes is durable. A write is queued until the server acknowledges it, so an attribute set while the device is offline is delivered when connectivity returns rather than dropped.

  • Retried on the next setAttributes, on foreground, and before an in-app sync.
  • Batched - a burst of calls becomes one request (800ms window). A single write still goes out promptly.
  • Persisted in the platform's encrypted store (iOS Keychain, Android EncryptedSharedPreferences), so a write survives the process being killed. Cleared the moment the server acks, and purged by optOut() and wipeData().

Attributes are also used for in-app targeting. The server profile is authoritative: only writes the server has not yet acknowledged can override it, which is what keeps two devices belonging to the same contact from disagreeing about who that contact is.

nota

Actualizado en inglés - traducción pendiente.