---
title: "Traiter les données des flows dans le SDK Kotlin Multiplatform"
description: "Enregistrez et utilisez les données saisies par vos utilisateurs dans les flows de votre application Kotlin Multiplatform avec le SDK Adapty."
---

> **AI agents**: to search Adapty docs faster and with fewer tokens, install the Adapty skill. Claude Code (self-updating via plugin): `claude plugin marketplace add adaptyteam/adapty-skills && claude plugin install adapty-skills@adapty` — other tools: `npx skills add adaptyteam/adapty-skills --all`

Lorsqu'un utilisateur saisit du texte dans un champ, répond à un quiz ou active un interrupteur dans un [flow](adapty-flow-builder), 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](kmp-setting-user-attributes) 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](adapty-flow-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 \{#receive-input-values\}

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` :

```kotlin showLineNumbers title="Kotlin"
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](kmp-flow-screen-views). 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`

```kotlin showLineNumbers title="Kotlin"
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](#before-you-start). 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 \{#when-your-app-receives-the-input\}

:::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](#delivery-and-limitations) 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 \{#what-you-receive\}

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 :

```json
{
  "name": "flow_user_input",
  "instanceId": "scr_registration",
  "isBackendEvent": false,
  "isCustomerEvent": true,
  "element_id": "email",
  "element_type": "email_input",
  "value": "jane@example.com"
}
```

| Paramètre         | Description |
|:------------------|:------------|
| `name`            | `flow_user_input` pour les événements de saisie, `flow_screen_showed` pour les affichages d'écran. |
| `instanceId`      | L'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_id`      | L'**Element ID** du [champ de saisie](builder-inputs-and-forms), ou le **Group ID** du [groupe sélectionnable](flow-selectable-elements). |
| `element_type`    | Le type d'élément qui a envoyé l'événement. Il détermine lequel des paramètres ci-dessous contient la valeur saisie. |
| `value`           | **Champs 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_ids`        | **Groupes 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_titles`     | **Groupes 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. |
| `isCustomerEvent` | Indicateur 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`. |
| `isBackendEvent`  | Indicateur 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 builder | `element_type` | Paramètre contenant la valeur | Ce qu'il contient |
|:----------------|:---------------|:------------------------------|:------------------|
| Champ **Texte**, **Nombre**, **Numéro de téléphone** | `text_input`, `number_input`, `phone_input` | `value` | La chaîne brute saisie par l'utilisateur. Les nombres arrivent sous forme de chaînes, et non de types numériques. |
| Champ **E-mail** | `email_input` | `value` | La 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 passe** | aucun | aucun | N'envoie aucun événement. |
| Champ **Date** | `date_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, à minuit heure locale de la date sélectionnée. |
| Champ **Heure** | `time_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, arrondi à la minute inférieure. |
| Champ **Date et heure** | `date_picker` et `time_picker` | `value` | Deux é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 **Type** | `date_time_picker` | `value` | Horodatage Unix en millisecondes, sous forme d'entier, arrondi à la minute inférieure. |
| Groupe à **choix unique** | `single_choice` | `item_ids`, `item_titles` | Deux 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 multiple** | `multi_choice` | `item_ids`, `item_titles` | Deux 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 **Bascule** | `toggle` | `value` | Un 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 \{#event-examples\}

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

<Details>
<summary>Text, email, number, and phone input (Click to expand)</summary>

```kotlin
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"
}
```
</Details>

<Details>
<summary>Sélecteurs de date, d'heure et de date-heure (Cliquez pour développer)</summary>

```kotlin
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)
}
```
</Details>

<Details>
<summary>Choix unique (Cliquez pour développer)</summary>

```kotlin
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"]
}
```
</Details>

<Details>
<summary>Choix multiple (cliquer pour développer)</summary>

```kotlin
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"]
}
```
</Details>

<Details>
<summary>Toggle (Click to expand)</summary>

```kotlin
override fun flowViewDidReceiveAnalyticEvent(
    view: AdaptyUIFlowView,
    name: String,
    paramsJsonString: String,
) {
    // paramsJsonString, once decoded:
    params["element_id"]       // "reminders"
    params["element_type"]     // "toggle"
    params["value"]            // true
}
```
</Details>

## Livraison et limitations \{#delivery-and-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 \{#use-cases\}

### Enregistrer les utilisateurs sur votre backend \{#register-users-on-your-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é.

```kotlin showLineNumbers title="Kotlin"
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 \{#enrich-user-profiles-with-data\}

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](kmp-setting-user-attributes) 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` :

```kotlin showLineNumbers title="Kotlin"
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 \{#customize-flows-shown-later\}

Les réponses au quiz peuvent aussi déterminer ce qu'un utilisateur voit lors d'un [placement](placements) 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](onboarding-quizzes) à votre flow. Donnez au [groupe sélectionnable](flow-selectable-elements) le Group ID `experience`, et à chaque option un Element ID explicite.
2. Traitez les réponses et [définissez des attributs personnalisés](kmp-setting-user-attributes) pour l'utilisateur.

```kotlin showLineNumbers title="Kotlin"
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
            }
    }
}
```

3. [Créez un segment](segments) pour chaque valeur d'attribut personnalisé.
4. Créez un [placement](placements) et ajoutez une [audience](audience) pour chaque segment.
5. [Affichez le flow](kmp-present-paywalls) pour ce placement dans votre application.