Procesar datos de flows en el SDK de Kotlin Multiplatform
Cuando un usuario escribe en un campo de texto, responde un cuestionario o activa un interruptor en un flow, el SDK pasa el valor a tu app a través de su callback de analíticas.
Las apps suelen utilizar esos datos para:
- Registrar usuarios en tu propio backend: Toma el correo electrónico y el nombre que un usuario introdujo en tu flow de onboarding y crea su cuenta cuando el flow se cierre.
- Guardar respuestas y preferencias: Registra lo que eligió el usuario para que tu app pueda utilizarlo más adelante; por ejemplo, escríbelo en su perfil de Adapty como atributos personalizados.
- Personalizar flows futuros: Guarda las respuestas del cuestionario como atributos personalizados y luego apunta a un placement posterior para que cada segmento vea un flow diferente, o un paywall distinto dentro de él.
- Alimentar plataformas de analítica de terceros: Reenvía las respuestas a Amplitude, Mixpanel o cualquier plataforma de analítica de producto que utilices.
Los inputs y los grupos de selección reportan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un Element ID significativo y a cada grupo de selección un Group ID en el builder.
Antes de comenzar
Necesitas:
- Adapty SDK v4 o posterior: Los callbacks de flow no existen en versiones anteriores.
- Un flow creado en el Flow & Paywall Builder: Solo los flows reportan valores de entrada a través de este callback.
- Una versión de flow publicada recientemente: Un flow solo reporta valores de entrada si lo publicaste después de que esta función estuviera disponible. Si no llega nada a tu app, publica una nueva versión del flow e inténtalo de nuevo.
Recibir valores de entrada
Los valores de entrada llegan al mismo callback que el resto de eventos analíticos de un flow, bajo el nombre de evento flow_user_input. Sobreescribe flowViewDidReceiveAnalyticEvent en el observador que registras con AdaptyUI.setFlowsEventsObserver:
AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
handleFlowInput(name, paramsJsonString)
}
})
El callback flowViewDidReceiveAnalyticEvent entrega todos los eventos de análisis de un flow, incluidas las vistas de pantalla. Los parámetros del evento llegan como una cadena JSON en paramsJsonString; decodifícala una vez y lee los campos.
- El parámetro
namecontiene el nombre del evento. Para filtrar los eventos de entrada del usuario, comparanameconflow_user_input. - El parámetro
element_typeindica la categoría del elemento. - El valor de la entrada se almacena en diferentes parámetros según el tipo de elemento:
- Los campos de texto, selectores y toggles guardan la entrada del usuario en
value - Los grupos seleccionables reportan las opciones activas en
item_idseitem_titles
- Los campos de texto, selectores y toggles guardan la entrada del usuario en
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val elementId = params["element_id"]?.jsonPrimitive?.content ?: return
// The screen the input sits on. Pair it with elementId to tell apart
// two fields that share an Element ID on different screens.
val screenId = params["instanceId"]?.jsonPrimitive?.contentOrNull
when (params["element_type"]?.jsonPrimitive?.content) {
"text_input", "email_input", "number_input", "phone_input" -> {
val text = params["value"]?.jsonPrimitive?.content
}
"date_picker", "time_picker", "date_time_picker" -> {
// Unix time in milliseconds. Read it as a double — Android serializes numbers that way.
val millis = params["value"]?.jsonPrimitive?.double?.toLong()
}
"single_choice" -> {
val optionId = params["item_ids"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content
}
"multi_choice" -> {
val optionIds = params["item_ids"]?.jsonArray?.map { it.jsonPrimitive.content }
}
"toggle" -> {
val isOn = params["value"]?.jsonPrimitive?.boolean
}
}
}
Para confirmar que el callback se ejecuta, interactúa con el input en una versión de prueba de tu propia app. Si tu callback handler no recibe ningún evento, revisa los prerequisitos. Asegúrate de que el flow fue publicado después de que esta función estuviera disponible.
Cuando tu app recibe el input
El valor predeterminado de entrada o selección nunca llega a tu aplicación a través de este callback. Si un usuario acepta la opción marcada como Set as default y continúa, no se dispara ningún evento. No interpretes la ausencia del evento como “sin respuesta”: el usuario simplemente dejó el valor predeterminado tal como estaba.
Los siguientes elementos disparan este evento:
- Campos de texto, email, número y teléfono
- Selectores de fecha, hora y fecha-hora
- Grupos seleccionables de opción única y múltiple, y toggles
Los siguientes no lo hacen:
- Campos de contraseña, selecciones de productos y cambios de pestaña
- Inputs dentro de un elemento Header, que se comparte entre pantallas
- Grupos seleccionables con IDs de opción duplicados o faltantes, o con un ID de grupo reutilizado en otra pantalla. Ese tipo de grupo no envía nada en absoluto, en lugar de enviar parte de la respuesta.
El evento se dispara cuando:
- Un campo pierde el foco. Un campo vaciado reporta una cadena vacía; un campo que el usuario nunca editó no reporta nada. Un marcador de posición no es un valor. Si el usuario vuelve al campo, lo edita y lo abandona de nuevo, se genera un segundo evento.
- El usuario cierra un selector tras elegir un nuevo valor. Cerrarlo sin cambios no reporta nada.
- El usuario pulsa una opción o un toggle. Un evento de opción múltiple lista todas las opciones seleccionadas, por lo que deseleccionar la última envía dos arrays vacíos.
El evento no se dispara cuando:
- El usuario escribe. No hay flujo de pulsaciones de teclas, solo el valor que tiene el campo cuando pierde el foco.
- El usuario envía o cierra el flow. Un valor que aún se está editando en ese momento puede perderse; la sección de limitaciones de entrega explica cómo diseñar la última pantalla teniendo esto en cuenta.
- Se establece un valor sin interacción del usuario. Una opción marcada como Set as default se preselecciona al abrirse la pantalla, y una acción Set Variable puede seleccionar una opción o rellenar un input a partir de otra interacción. Ninguna de las dos envía un evento; un input prerrellenado solo se reporta una vez que el usuario lo edita.
Qué recibes
El callback entrega dos eventos. Filtra por name para mostrar únicamente los eventos flow_user_input. El payload JSON de respuesta tiene este aspecto:
{
"name": "flow_user_input",
"instanceId": "scr_registration",
"isBackendEvent": false,
"isCustomerEvent": true,
"element_id": "email",
"element_type": "email_input",
"value": "jane@example.com"
}
| Parámetro | Descripción |
|---|---|
name | flow_user_input para eventos de entrada, flow_screen_showed para vistas de pantalla. |
instanceId | El ID de la pantalla del input. Los IDs de elemento son únicos dentro de una pantalla, no en todo el flow. Si tu flow tiene inputs en más de una pantalla, combina instanceId con element_id al filtrar eventos. |
element_id | El Element ID del input, o el Group ID del grupo seleccionable. |
element_type | El tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input. |
value | Solo campos de texto, selectores y toggles. El valor del input: una cadena para campos de texto, un entero para selectores, un booleano para toggles. |
item_ids | Solo grupos seleccionables de elección única y múltiple. Los Element IDs de las opciones seleccionadas, en el orden en que aparecen en el builder. Una entrada para un grupo de elección única; cualquier número para un grupo de elección múltiple. |
item_titles | Solo grupos seleccionables de elección única y múltiple. Los títulos de las opciones listadas en item_ids, en el mismo orden. Nunca está vacío: una opción sin título informa su ID en su lugar. |
isCustomerEvent | Indicador de utilidad, siempre true para este evento. Marca los eventos que el SDK entrega a tu callback. Resulta útil si un mismo handler reenvía todos los eventos del flow a tu sistema de análisis y filtras por este indicador en lugar de por name. |
isBackendEvent | Indicador de utilidad, siempre false para este evento. Marca los eventos que Adapty también registra para su propio análisis. false confirma que lo que los usuarios introducen llega a tu app y solo a ella — Adapty no lo recibe ni lo almacena. |
Lo que reporta cada elemento:
| En el builder | element_type | Parámetro que almacena el valor | Qué contiene |
|---|---|---|---|
| Input de Texto, Número, Número de teléfono | text_input, number_input, phone_input | value | La cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos. |
| Input de E-mail | email_input | value | La cadena literal que escribió el usuario, aunque no haya superado la validación de formato del builder. Valídala en tu lado antes de usarla. |
| Input de Contraseña | ninguno | ninguno | No envía ningún evento. |
| Input de Fecha | date_picker | value | Tiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada. |
| Input de Hora | time_picker | value | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. |
| Input de Fecha y hora | date_picker y time_picker | value | Dos elementos, un selector de fecha y un selector de hora. Cada uno envía su propio evento. |
| Input cambiado a Fecha y hora en el desplegable Type | date_time_picker | value | Tiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto. |
| Grupo de elección única | single_choice | item_ids, item_titles | Dos arrays. item_ids: un array con el Element ID de la opción seleccionada. item_titles: un array con el título de esa opción. |
| Grupo de elección múltiple | multi_choice | item_ids, item_titles | Dos arrays. item_ids: los Element IDs de todas las opciones seleccionadas, en el orden en que aparecen en el builder. item_titles: sus títulos, en el mismo orden. Ambos arrays están vacíos cuando no hay nada seleccionado. |
| Grupo de Toggle | toggle | value | Un booleano. |
Para bifurcar según una respuesta, compara item_ids, no item_titles. El título es derivado: el Element Title de la opción si lo definiste, si no su texto en tu idioma predeterminado, y si no su Element ID. Un usuario que vio el flow en otro idioma vio un texto diferente.
Ejemplos de eventos
Estos ejemplos muestran las propiedades disponibles en cada evento, con valores ilustrativos en los comentarios.
Entrada de texto, email, número y teléfono (Haz clic para expandir)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
name // "flow_user_input"
paramsJsonString // "{\"name\":\"flow_user_input\",\"instanceId\":\"scr_J260KU5q\",\"isBackendEvent\":false,\"isCustomerEvent\":true,\"element_id\":\"email\",\"element_type\":\"email_input\",\"value\":\"jane@example.com\"}"
// paramsJsonString, una vez decodificado:
params["name"] // "flow_user_input"
params["instanceId"] // "scr_J260KU5q"
params["isCustomerEvent"] // true
params["isBackendEvent"] // false
params["element_id"] // "email"
params["element_type"] // "email_input"
params["value"] // "jane@example.com"
} Selectores de fecha, hora y fecha-hora (haz clic para expandir)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "birthday"
params["element_type"] // "date_picker"
params["value"] // 645408000000 (Unix milliseconds — 1990-06-15, local midnight)
} Opción única (haz clic para expandir)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "experience"
params["element_type"] // "single_choice"
params["item_ids"] // ["pro"]
params["item_titles"] // ["I train professionally"]
} Multi choice (Click to expand)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "interests"
params["element_type"] // "multi_choice"
params["item_ids"] // ["sports", "music"]
params["item_titles"] // ["Sports", "Music"]
} Toggle (Click to expand)
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
// paramsJsonString, once decoded:
params["element_id"] // "reminders"
params["element_type"] // "toggle"
params["value"] // true
} Entrega y limitaciones
Los flows envían valores sin procesar: direcciones de correo electrónico, números de teléfono y cualquier otra cosa que el usuario escriba. Trata todo lo que entregue el callback de analíticas como datos personales y no los almacenes en ningún lugar donde no guardarías el correo electrónico de un usuario.
- El último valor prevalece: recibes un evento por campo con el valor con el que el usuario se quedó, no una secuencia tecla a tecla. Si edita un campo, solo llega la última versión.
- Sin garantía de envío: los valores te llegan a medida que el usuario avanza por el flow, y puede salir en cualquier momento. Espera a que el flow se cierre antes de considerar un conjunto de respuestas como completo.
- Entrega de mejor esfuerzo: si un usuario cierra el flow o minimiza la app mientras un campo tiene el foco o un selector está abierto, ese valor puede perderse.
Para que el valor del último campo sea fiable, termina el flow con una pantalla que no tenga entradas ni selectores, y cierra el flow mediante una acción explícita del usuario en lugar de hacerlo automáticamente al aparecer esa pantalla. Pasar a la pantalla final quita el foco del campo anterior, que es lo que envía su valor.
Almacena cada valor de entrada a medida que lo recibe tu observer y envía el conjunto completo cuando el flow se cierre. Para capturar ese momento, sobreescribe flowViewDidDisappear en el mismo observer. Se ejecuta cuando la vista del flow se cierra, tanto si el usuario completó el flow como si lo cerró a mitad.
Casos de uso
Registrar usuarios en tu backend
Recopila los valores según lleguen y envíalos una vez que el flow se cierre, de modo que una sola solicitud incluya el conjunto completo de respuestas.
La vista desaparece tanto si el usuario completó el flow como si lo abandonó a mitad. Verifica los campos que necesitas antes de llamar a tu backend.
El flow no puede mostrar errores de tu backend. El callback no tiene valor de retorno y el SDK no dispone de ningún método para enviar datos a un flow en ejecución. Si el registro falla, por ejemplo porque el correo electrónico ya está en uso, muestra el error en tu propia UI una vez que el flow se haya cerrado.
class MyFlowsEventsObserver : AdaptyUIFlowsEventsObserver {
private val flowAnswers = mutableMapOf<String, String>()
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String,
) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val elementId = params["element_id"]?.jsonPrimitive?.content ?: return
val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return
flowAnswers[elementId] = value
}
override fun flowViewDidDisappear(view: AdaptyUIFlowView) {
if (!flowAnswers.containsKey("email")) return
// Send flowAnswers to your backend here to create the account.
flowAnswers.clear()
}
}
Enriquecer perfiles de usuario con datos
Para vincular lo que un usuario ha introducido a su perfil y evitar pedirle los mismos datos dos veces, actualiza el perfil de usuario a medida que lleguen los valores.
Por ejemplo, si tu flow tiene un campo de texto con el ID de elemento name y un campo de email con el ID de elemento email:
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return
val builder = AdaptyProfileParameters.Builder()
when (params["element_id"]?.jsonPrimitive?.content) {
"name" -> builder.withFirstName(value)
"email" -> builder.withEmail(value)
else -> return
}
mainUiScope.launch {
Adapty.updateProfile(builder.build())
.onError { error ->
// handle the error
}
}
}
Personalizar flows que se muestran más adelante
Las respuestas del quiz también pueden decidir qué ve un usuario en un placement posterior: un flow diferente o un paywall distinto dentro de él.
Por ejemplo, pregunta a los usuarios sobre su experiencia con el deporte en tu flow de onboarding y, a continuación, muestra a cada grupo su propio flow con distintos productos y textos.
- Añade un quiz a tu flow. Asigna al grupo seleccionable el Group ID
experiencey a cada opción un Element ID descriptivo. - Gestiona las respuestas y establece atributos personalizados para el usuario.
private fun handleFlowInput(name: String, paramsJsonString: String) {
if (name != "flow_user_input") return
val params = Json.parseToJsonElement(paramsJsonString).jsonObject
if (params["element_id"]?.jsonPrimitive?.content != "experience") return
val optionId = params["item_ids"]?.jsonArray?.firstOrNull()
?.jsonPrimitive?.contentOrNull ?: return
val builder = AdaptyProfileParameters.Builder()
// Set the custom attribute 'experience' to the option the user selected
// (beginner, amateur, or pro).
builder.withCustomAttribute("experience", optionId)
mainUiScope.launch {
Adapty.updateProfile(builder.build())
.onError { error ->
// handle the error
}
}
}
- Crea un segmento para cada valor de atributo personalizado.
- Crea un placement y añade una audiencia para cada segmento.
- Muestra el flow de ese placement en tu app.