Procesar datos de flows en iOS SDK

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 análisis.

Las apps suelen usar esos datos para:

  • Registrar usuarios en tu propio backend: Recoge el correo electrónico y el nombre que el usuario introdujo en tu onboarding flow, 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.
  • Enviar datos a plataformas de analítica de terceros: Reenvía las respuestas a Amplitude, Mixpanel o cualquier otra herramienta de analítica de producto que utilices.

Los inputs y los grupos seleccionables informan sus valores automáticamente. Para distinguirlos en tu código, asigna a cada input un Element ID significativo y a cada grupo seleccionable un Group ID en el builder.

Antes de empezar

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 de analítica de un flow, bajo el nombre de evento flow_user_input. Registra el callback junto con tus otros manejadores de eventos del flow.

El closure y el método delegado reciben los mismos dos argumentos, por lo que el código que lee el valor es idéntico en ambos casos.

El callback didReceiveAnalyticEvent entrega todos los eventos de análisis de un flow, incluidas las vistas de pantalla.

  • El parámetro name contiene el nombre del evento. Para filtrar los eventos de entrada del usuario, compara name con flow_user_input.
  • El parámetro element_type indica la categoría del elemento.
  • El valor de la entrada se almacena en distintos 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_ids e item_titles
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let elementType = params["element_type"] as? String
    else { return }

    // The screen the input sits on. Pair it with elementId to tell apart
    // two fields that share an Element ID on different screens.
    let screenId = params["instanceId"] as? String

    switch elementType {
    case "text_input", "email_input", "number_input", "phone_input":
        let text = params["value"] as? String
    case "date_picker", "time_picker", "date_time_picker":
        // Integer Unix time in milliseconds, not the seconds Date expects.
        let date = (params["value"] as? Int).map { Date(timeIntervalSince1970: Double($0) / 1000) }
    case "single_choice":
        let optionId = (params["item_ids"] as? [String])?.first
    case "multi_choice":
        let optionIds = params["item_ids"] as? [String]
    case "toggle":
        let isOn = params["value"] as? Bool
    default:
        break
    }
}

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 la entrada

Important

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ámetroDescripción
nameflow_user_input para eventos de entrada, flow_screen_showed para vistas de pantalla.
instanceIdEl 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_idEl Element ID del input, o el Group ID del grupo seleccionable.
element_typeEl tipo de elemento que envió el evento. Determina cuál de los parámetros siguientes contiene el valor del input.
valueSolo 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_idsSolo 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_titlesSolo 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.
isCustomerEventIndicador 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.
isBackendEventIndicador 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 builderelement_typeParámetro que almacena el valorQué contiene
Input de Texto, Número, Número de teléfonotext_input, number_input, phone_inputvalueLa cadena literal que escribió el usuario. Los números llegan como cadenas, no como tipos numéricos.
Input de E-mailemail_inputvalueLa 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ñaningunoningunoNo envía ningún evento.
Input de Fechadate_pickervalueTiempo Unix en milisegundos, como entero, a medianoche local de la fecha seleccionada.
Input de Horatime_pickervalueTiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto.
Input de Fecha y horadate_picker y time_pickervalueDos 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 Typedate_time_pickervalueTiempo Unix en milisegundos, como entero, redondeado hacia abajo al minuto.
Grupo de elección únicasingle_choiceitem_ids, item_titlesDos 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últiplemulti_choiceitem_ids, item_titlesDos 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 ToggletogglevalueUn 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, correo electrónico, número y teléfono (haz clic para expandir)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    name;                      // "flow_user_input"
    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"   (String)
}
Selectores de fecha, hora y fecha-hora (Click to expand)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "birthday"
    params["element_type"];    // "date_picker"
    params["value"];           // 645408000000   (Unix milliseconds — 1990-06-15, local midnight)
}
Single choice (Click to expand)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "experience"
    params["element_type"];    // "single_choice"
    params["item_ids"];        // ["pro"]
    params["item_titles"];     // ["I train professionally"]
}
Multi choice (Click to expand)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "interests"
    params["element_type"];    // "multi_choice"
    params["item_ids"];        // ["sports", "music"]
    params["item_titles"];     // ["Sports", "Music"]
}
Toggle (Click to expand)
func handleFlowInput(name: String, params: [String: any Sendable]) {
    params["element_id"];      // "reminders"
    params["element_type"];    // "toggle"
    params["value"];           // true   (Bool)
}

Entrega y limitaciones

Warning

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 tal como lo recibe tu manejador y envía el conjunto completo cuando el flow se cierre. Para capturar ese momento, implementa flowControllerDidDisappear en tu AdaptyFlowControllerDelegate en UIKit, o pasa un closure didDisappear al modificador .flow en SwiftUI. Ambos se ejecutan después de que la vista del flow ha salido de la pantalla, tanto si el usuario completó el flow como si lo descartó.

Casos de uso

Registrar usuarios en tu backend

Recoge los valores conforme llegan y envíalos una vez que el flow se cierre, de modo que una sola solicitud lleve un conjunto completo de respuestas.

La vista desaparece tanto si el usuario completó el flow como si lo abandonó a medias. Comprueba 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 que envíe datos a un flow en ejecución. Si el registro falla, por ejemplo porque el correo ya está en uso, muestra el error en tu propia interfaz una vez que el flow se cierre.

private var flowAnswers: [String: String] = [:]

func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    flowAnswers[elementId] = value
}

func flowControllerDidDisappear(_ controller: AdaptyFlowController) {
    guard flowAnswers["email"] != nil else { return }

    // Send flowAnswers to your backend here to create the account.

    flowAnswers.removeAll()
}

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 los valores lleguen.

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:

func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          let elementId = params["element_id"] as? String,
          let value = params["value"] as? String
    else { return }

    let builder = AdaptyProfileParameters.Builder()

    switch elementId {
    case "name":
        builder.with(firstName: value)
    case "email":
        builder.with(email: value)
    default:
        return
    }

    // Delegate methods are synchronous; kick off the async update in a Task.
    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // handle the error
        }
    }
}

Personalizar los flows que se muestran más adelante

Las respuestas del quiz también pueden determinar lo que un usuario ve 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 luego muestra a cada grupo su propio flow con diferentes productos y textos.

  1. Añade un quiz a tu flow. Asigna al grupo seleccionable el Group ID experience y a cada opción un Element ID significativo.
  2. Gestiona las respuestas y establece atributos personalizados para el usuario.
func handleFlowInput(name: String, params: [String: any Sendable]) {
    guard name == "flow_user_input",
          params["element_id"] as? String == "experience",
          let optionId = (params["item_ids"] as? [String])?.first
    else { return }

    let builder = AdaptyProfileParameters.Builder()
    // Set the custom attribute 'experience' to the option the user selected
    // (beginner, amateur, or pro).
    try? builder.with(customAttribute: optionId, forKey: "experience")

    Task {
        do {
            try await Adapty.updateProfile(params: builder.build())
        } catch {
            // handle the error
        }
    }
}
  1. Crea un segmento para cada valor de atributo personalizado.
  2. Crea un placement y añade una audiencia para cada segmento.
  3. Muestra el flow de ese placement en tu app.