Traiter les données des flows dans le SDK Kotlin Multiplatform

Lorsqu’un utilisateur saisit du texte dans un champ, répond à un quiz ou active un interrupteur dans un flow, le SDK transmet la valeur à votre application via son callback d’analyse.

Les applications utilisent généralement ces données pour :

  • Enregistrer les utilisateurs sur votre propre backend : Récupérez l’adresse e-mail et le nom saisis dans votre flow d’onboarding, puis créez leur compte à la fermeture du flow.
  • Sauvegarder les réponses et préférences : Gardez une trace de ce que l’utilisateur a choisi pour que votre application puisse s’en servir plus tard — par exemple, écrivez ces données dans son profil Adapty sous forme d’attributs personnalisés.
  • Personnaliser les flows futurs : Enregistrez les réponses au quiz comme attributs personnalisés, puis ciblez un placement ultérieur pour que chaque segment reçoive un flow différent, ou un paywall différent à l’intérieur de celui-ci.
  • Alimenter des plateformes d’analyse tierces : Transmettez les réponses à Amplitude, Mixpanel, ou toute autre plateforme d’analyse produit que vous utilisez.

Les entrées et les groupes sélectionnables transmettent automatiquement leurs valeurs. Pour les distinguer dans votre code, attribuez un Element ID pertinent à chaque entrée et un Group ID à chaque groupe sélectionnable dans le builder.

Avant de commencer

Vous avez besoin de :

  • Adapty SDK v4 ou supérieur : Les callbacks de flow n’existent pas dans les versions antérieures.
  • Un flow créé dans le Flow & Paywall Builder : Seuls les flows transmettent les valeurs d’entrée via ce callback.
  • Une version de flow publiée récemment : Un flow ne transmet les valeurs d’entrée que si vous l’avez publié après que cette fonctionnalité soit devenue disponible. Si rien ne parvient à votre application, publiez une nouvelle version du flow et réessayez.

Recevoir les valeurs saisies

Les valeurs saisies parviennent au même callback que tous les autres événements analytiques d’un flow, sous le nom d’événement flow_user_input. Surchargez flowViewDidReceiveAnalyticEvent sur l’observer que vous enregistrez avec AdaptyUI.setFlowsEventsObserver :

AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {

    override fun flowViewDidReceiveAnalyticEvent(
        view: AdaptyUIFlowView,
        name: String,
        paramsJsonString: String,
    ) {
        handleFlowInput(name, paramsJsonString)
    }
})

Le callback flowViewDidReceiveAnalyticEvent transmet tous les événements analytiques d’un flow, y compris les vues d’écran. Les paramètres de l’événement arrivent sous forme d’une seule chaîne JSON dans paramsJsonString ; décodez-la une seule fois et lisez les champs.

  • Le paramètre name contient le nom de l’événement. Pour filtrer les événements de saisie utilisateur, comparez name à flow_user_input.
  • Le paramètre element_type indique la catégorie de l’élément.
  • La valeur de la saisie est stockée dans des paramètres différents selon le type d’élément :
    • Les champs texte, les sélecteurs et les bascules stockent la saisie de l’utilisateur dans value
    • Les groupes sélectionnables reportent les options actives dans item_ids et item_titles
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
        }
    }
}

Pour confirmer que le callback se déclenche, interagissez avec le champ de saisie dans une version de test de votre application. Si votre gestionnaire de callback ne reçoit pas d’événement, vérifiez les prérequis. Assurez-vous que le flow a bien été publié après que cette fonctionnalité est devenue disponible.

Quand votre application reçoit les données d’entrée

Important

La valeur saisie ou sélectionnée par défaut n’est jamais transmise à votre application via ce callback. Si un utilisateur accepte l’option marquée Set as default et continue, aucun événement ne se déclenche. Ne considérez pas l’absence d’événement comme « aucune réponse » — l’utilisateur a simplement conservé la valeur par défaut.

Les éléments suivants déclenchent cet événement :

  • Champs texte, e-mail, numérique et téléphone
  • Sélecteurs de date, d’heure et de date-heure
  • Groupes sélectionnables à choix unique et à choix multiple, et bascules

Les éléments suivants ne le déclenchent pas :

  • Champs de mot de passe, sélections de produits et changements d’onglet
  • Les saisies à l’intérieur d’un élément Header, partagé entre les écrans
  • Les groupes sélectionnables avec des identifiants d’élément dupliqués ou manquants, ou dont l’identifiant de groupe est réutilisé sur un autre écran. Un tel groupe n’envoie rien du tout, plutôt qu’une réponse partielle.

L’événement se déclenche lorsque :

  • Un champ perd le focus. Un champ vidé signale une chaîne vide ; un champ que l’utilisateur n’a jamais modifié ne signale rien. Un espace réservé n’est pas une valeur. Si l’utilisateur revient sur le champ, le modifie et le quitte à nouveau, un second événement est émis.
  • L’utilisateur ferme un sélecteur après avoir choisi une nouvelle valeur. Le fermer sans modification ne signale rien.
  • L’utilisateur appuie sur une option ou une bascule. Un événement à choix multiple liste toutes les options sélectionnées ; désélectionner la dernière envoie donc deux tableaux vides.

L’événement ne se déclenche pas lorsque :

  • L’utilisateur saisit du texte. Il n’y a pas de flux de frappes clavier, seulement la valeur que contient le champ au moment où il perd le focus.
  • L’utilisateur soumet ou ferme le flow. Une valeur encore en cours de saisie à cet instant peut être perdue ; la section limites de livraison explique comment concevoir le dernier écran en tenant compte de cela.
  • Une valeur est définie sans interaction de l’utilisateur. Une option marquée Set as default est présélectionnée à l’ouverture de l’écran, et une action Set Variable peut sélectionner une option ou remplir un champ depuis une autre interaction. Aucune des deux n’envoie d’événement ; une saisie préremplie n’est signalée que lorsque l’utilisateur la modifie.

Ce que vous recevez

Le callback transmet deux événements. Filtrez par name pour n’afficher que les événements flow_user_input. La charge utile JSON de la réponse ressemble à ceci :

{
  "name": "flow_user_input",
  "instanceId": "scr_registration",
  "isBackendEvent": false,
  "isCustomerEvent": true,
  "element_id": "email",
  "element_type": "email_input",
  "value": "jane@example.com"
}
ParamètreDescription
nameflow_user_input pour les événements de saisie, flow_screen_showed pour les affichages d’écran.
instanceIdL’ID de l’écran contenant le champ de saisie. Les IDs d’éléments sont uniques au sein d’un écran, pas dans l’ensemble du flow. Si votre flow comporte des champs de saisie sur plusieurs écrans, combinez instanceId avec element_id pour filtrer les événements.
element_idL’Element ID du champ de saisie, ou le Group ID du groupe sélectionnable.
element_typeLe type d’élément qui a envoyé l’événement. Il détermine lequel des paramètres ci-dessous contient la valeur saisie.
valueChamps texte, sélecteurs et bascules uniquement. La valeur saisie : une chaîne pour les champs texte, un entier pour les sélecteurs, un booléen pour les bascules.
item_idsGroupes sélectionnables à choix unique et à choix multiple uniquement. Les Element IDs des options sélectionnées, dans l’ordre d’apparition des options dans le builder. Une entrée pour un groupe à choix unique ; un nombre quelconque pour un groupe à choix multiple.
item_titlesGroupes sélectionnables à choix unique et à choix multiple uniquement. Les titres des options listées dans item_ids, dans le même ordre. Jamais vide : une option sans titre renvoie son ID à la place.
isCustomerEventIndicateur utilitaire, toujours true pour cet événement. Il identifie les événements que le SDK transmet à votre callback. Utile si un seul handler transfère tous les événements du flow vers vos analytics et que vous filtrez sur cet indicateur plutôt que sur name.
isBackendEventIndicateur utilitaire, toujours false pour cet événement. Il identifie les événements qu’Adapty enregistre également pour ses propres analytics. false confirme que ce que les utilisateurs saisissent ne parvient qu’à votre application — Adapty ne le reçoit pas et ne le stocke pas.

Ce que chaque élément remonte :

Dans le builderelement_typeParamètre contenant la valeurCe qu’il contient
Champ Texte, Nombre, Numéro de téléphonetext_input, number_input, phone_inputvalueLa chaîne brute saisie par l’utilisateur. Les nombres arrivent sous forme de chaînes, et non de types numériques.
Champ E-mailemail_inputvalueLa chaîne brute saisie par l’utilisateur, même si elle n’a pas passé la validation de format du builder. Validez-la de votre côté avant de l’utiliser.
Champ Mot de passeaucunaucunN’envoie aucun événement.
Champ Datedate_pickervalueHorodatage Unix en millisecondes, sous forme d’entier, à minuit heure locale de la date sélectionnée.
Champ Heuretime_pickervalueHorodatage Unix en millisecondes, sous forme d’entier, arrondi à la minute inférieure.
Champ Date et heuredate_picker et time_pickervalueDeux éléments : un sélecteur de date et un sélecteur d’heure. Chacun envoie son propre événement.
Champ passé en mode Date et heure dans le menu déroulant Typedate_time_pickervalueHorodatage Unix en millisecondes, sous forme d’entier, arrondi à la minute inférieure.
Groupe à choix uniquesingle_choiceitem_ids, item_titlesDeux tableaux. item_ids : un tableau contenant l’Element ID de l’option sélectionnée. item_titles : un tableau contenant le titre de cette option.
Groupe à choix multiplemulti_choiceitem_ids, item_titlesDeux tableaux. item_ids : les Element IDs de toutes les options sélectionnées, dans l’ordre d’apparition dans le builder. item_titles : leurs titres, dans le même ordre. Les deux tableaux sont vides si rien n’est sélectionné.
Groupe BasculetogglevalueUn booléen.

Pour brancher votre logique selon une réponse, comparez item_ids, pas item_titles. Un titre est dérivé : l’Element Title de l’option si vous en avez défini un, sinon son texte dans votre langue par défaut, sinon son Element ID. Un utilisateur ayant consulté le flow dans une autre langue a vu un texte différent.

Exemples d’événements

Ces exemples montrent les propriétés disponibles sur chaque événement, avec des valeurs illustratives en commentaires.

Text, email, number, and phone input (Click to expand)
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, once decoded:
    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"
}
Sélecteurs de date, d’heure et de date-heure (Cliquez pour développer)
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)
}
Choix unique (Cliquez pour développer)
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"]
}
Choix multiple (cliquer pour développer)
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
}

Livraison et limitations

Warning

Les flows transmettent les valeurs brutes — adresses e-mail, numéros de téléphone et tout ce qu’un utilisateur saisit. Traitez tout ce que le callback analytics vous fournit comme des données personnelles, et ne les écrivez nulle part où vous n’écririez pas l’adresse e-mail d’un utilisateur.

  • La dernière valeur gagne : Vous recevez un événement par champ, portant la valeur sur laquelle l’utilisateur s’est arrêté plutôt qu’un flux touche par touche. S’il modifie un champ, seule la dernière version vous parvient.
  • Aucune garantie d’envoi : Les valeurs vous parviennent au fur et à mesure que les utilisateurs avancent dans le flow, et un utilisateur peut partir à tout moment. Attendez que le flow se ferme avant de considérer un ensemble de réponses comme complet.
  • Distribution au mieux : Si un utilisateur ferme le flow ou réduit l’application pendant qu’un champ est encore actif ou qu’un sélecteur est encore ouvert, cette valeur peut être perdue.

Pour que la valeur du dernier champ soit fiable, terminez le flow par un écran sans champs de saisie ni sélecteurs, et fermez le flow depuis une action explicite de l’utilisateur plutôt qu’automatiquement à l’apparition de cet écran. Passer à l’écran final retire le focus du champ précédent, ce qui déclenche l’envoi de sa valeur.

Stockez chaque valeur d’entrée au fur et à mesure que votre observer la reçoit, et envoyez l’ensemble complet lorsque le flow se ferme. Pour intercepter ce moment, surchargez flowViewDidDisappear sur le même observer. Cette méthode s’exécute lorsque la vue du flow est masquée, que l’utilisateur ait terminé le flow ou l’ait fermé en cours de route.

Cas d’utilisation

Enregistrer les utilisateurs sur votre backend

Collectez les valeurs au fur et à mesure qu’elles arrivent et envoyez-les en une seule fois à la fermeture du flow, afin qu’une seule requête transmette un ensemble complet de réponses.

La vue disparaît que l’utilisateur ait terminé le flow ou l’ait abandonné en cours de route. Vérifiez que les champs dont vous avez besoin sont bien renseignés avant d’appeler votre backend.

Le flow ne peut pas afficher les erreurs provenant de votre backend. Le callback n’a pas de valeur de retour, et le SDK ne fournit aucune méthode pour envoyer des données dans un flow en cours d’exécution. Si l’inscription échoue, par exemple parce que l’adresse e-mail est déjà utilisée, affichez l’erreur dans votre propre interface une fois le flow fermé.

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()
    }
}

Enrichir les profils utilisateurs avec des données

Pour associer les informations saisies par un utilisateur à son profil et éviter de lui redemander les mêmes données, mettez à jour le profil utilisateur au fur et à mesure de la saisie.

Par exemple, si votre flow comporte un champ texte avec l’ID d’élément name et un champ e-mail avec l’ID d’élément 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
            }
    }
}

Personnaliser les flows affichés ultérieurement

Les réponses au quiz peuvent aussi déterminer ce qu’un utilisateur voit lors d’un placement ultérieur — un flow différent, ou un paywall différent à l’intérieur.

Par exemple, demandez aux utilisateurs leur niveau d’expérience sportive dans votre flow d’onboarding, puis montrez à chaque groupe son propre flow avec des produits et des textes adaptés.

  1. Ajoutez un quiz à votre flow. Donnez au groupe sélectionnable le Group ID experience, et à chaque option un Element ID explicite.
  2. Traitez les réponses et définissez des attributs personnalisés pour l’utilisateur.
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
            }
    }
}
  1. Créez un segment pour chaque valeur d’attribut personnalisé.
  2. Créez un placement et ajoutez une audience pour chaque segment.
  3. Affichez le flow pour ce placement dans votre application.