---
title: "Обработка данных из флоу в Android SDK"
description: "Сохраняйте и используйте данные, которые вводят пользователи во флоу Android-приложения, с помощью Adapty SDK."
---

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

Когда пользователь вводит текст в поле ввода, отвечает на вопрос викторины или переключает тоггл во [флоу](adapty-flow-builder), SDK передаёт значение в ваше приложение через callback аналитики.

Чаще всего приложения используют эти данные для:

- **Регистрируйте пользователей на своём бэкенде**: Берите email и имя, которые пользователь ввёл в онбординге, и создавайте аккаунт, когда флоу закрывается.
- **Сохраняйте ответы и предпочтения**: Отслеживайте, что выбрал пользователь, чтобы приложение могло использовать это позже — например, [записывайте данные в профиль Adapty](android-setting-user-attributes) как пользовательские атрибуты.
- **Настраивайте последующие флоу**: Сохраняйте ответы на вопросы квиза как пользовательские атрибуты, затем настраивайте таргетинг на нужный плейсмент, чтобы каждый сегмент видел свой флоу или свой пейвол внутри него.
- **Передавайте данные в сторонние аналитические платформы**: Отправляйте ответы в Amplitude, Mixpanel или любую другую аналитическую систему, которую вы используете.

Поля ввода и группы выбора автоматически передают свои значения. Чтобы различать их в коде, задайте каждому полю ввода уникальный **Element ID**, а каждой группе выбора — **Group ID** в билдере.

## Прежде чем начать \{#before-you-start\}

Вам понадобится:

- **Adapty SDK v4 или новее**: колбэки флоу отсутствуют в более ранних версиях.
- **Флоу, собранный в [Flow & Paywall Builder](adapty-flow-builder)**: только флоу передают входные значения через этот колбэк.
- **Недавно опубликованная версия флоу**: флоу передаёт входные значения только если вы опубликовали его после того, как эта функция стала доступна. Если ничего не приходит в приложение, опубликуйте новую версию флоу и попробуйте снова.

## Получение введённых значений \{#receive-input-values\}

Введённые значения поступают в тот же колбэк, что и все остальные аналитические события флоу, под именем события `flow_user_input`. Переопределите `onAnalyticEvent` в слушателе событий флоу:

```kotlin showLineNumbers title="Kotlin"
class MyFlowEventListener : AdaptyFlowDefaultEventListener() {

    override fun onAnalyticEvent(
        name: String,
        params: Map<String, Any?>,
        context: Context,
    ) {
        handleFlowInput(name, params)
    }
}
```

Колбэк `onAnalyticEvent` доставляет все аналитические события из флоу, включая [просмотры экранов](android-flow-screen-views).
- Параметр `name` содержит название события. Чтобы отфильтровать события пользовательского ввода, сравните `name` со значением `flow_user_input`.
- Параметр `element_type` указывает категорию элемента.
- Значение ввода хранится в разных параметрах в зависимости от типа элемента:
     - Текстовые поля, пикеры и переключатели хранят пользовательский ввод в `value`
     - Группы с выбором сообщают об активных опциях через `item_ids` и `item_titles`

```kotlin showLineNumbers title="Kotlin"
private fun handleFlowInput(name: String, params: Map<String, Any?>) {
    if (name != "flow_user_input") return

    val elementId = params["element_id"] as? String ?: 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"] as? String

    when (params["element_type"]) {
        "text_input", "email_input", "number_input", "phone_input" -> {
            val text = params["value"] as? String
        }
        "date_picker", "time_picker", "date_time_picker" -> {
            // Unix time in milliseconds. Numbers arrive as Double — read them through Number.
            val millis = (params["value"] as? Number)?.toLong()
        }
        "single_choice" -> {
            val optionId = (params["item_ids"] as? List<*>)?.firstOrNull() as? String
        }
        "multi_choice" -> {
            val optionIds = (params["item_ids"] as? List<*>)?.filterIsInstance<String>()
        }
        "toggle" -> {
            val isOn = params["value"] as? Boolean
        }
    }
}
```

Чтобы убедиться, что коллбэк срабатывает, взаимодействуйте с полем ввода в тестовой сборке вашего приложения. Если обработчик коллбэка не получает событие, проверьте [предварительные требования](#before-you-start). Убедитесь, что флоу был опубликован после того, как эта функция стала доступной.

## Когда приложение получает входные данные \{#when-your-app-receives-the-input\}

:::important
Значение по умолчанию или предвыбранный вариант никогда не попадают в ваше приложение через этот колбэк. Если пользователь принимает опцию, отмеченную **Set as default**, и продолжает, событие не срабатывает. Отсутствие события не означает «нет ответа» — пользователь просто оставил значение по умолчанию.
:::

**Следующие элементы вызывают это событие:**

- Текстовые, email-, числовые и телефонные поля
- Выборщики даты, времени и даты со временем
- Группы с единственным и множественным выбором, а также переключатели

**Следующие не вызывают:**

- Поля пароля, выборы продукта и переключения вкладок
- Поля ввода внутри элемента Header, который является общим для всех экранов
- Группы с выбором, у которых есть дублирующиеся или отсутствующие Element ID у опций, либо Group ID, повторно используемый на другом экране. Такая группа не отправляет ничего, а не часть ответа.

**Событие срабатывает когда:**

- Поле теряет фокус. Очищенное поле сообщает пустую строку; поле, которое пользователь ни разу не редактировал, не сообщает ничего. Плейсхолдер не является значением. Если пользователь возвращается к полю, редактирует его и снова уходит, следует второе событие.
- Пользователь закрывает выборщик после выбора нового значения. Закрытие без изменений не сообщает ничего.
- Пользователь нажимает на опцию или переключатель. Событие множественного выбора содержит список всех выбранных опций, поэтому снятие последней выбранной опции отправляет два пустых массива.

**Событие не срабатывает когда:**

- Пользователь набирает текст. Потока нажатий клавиш нет — только значение, которое поле содержит в момент потери фокуса.
- Пользователь отправляет или закрывает флоу. Значение, которое в этот момент ещё редактируется, может быть потеряно; в разделе [ограничения доставки](#delivery-and-limitations) описано, как спроектировать последний экран с учётом этого.
- Значение устанавливается без взаимодействия с пользователем. Опция, отмеченная **Set as default**, предвыбирается при открытии экрана, а действие **Set Variable** может выбрать опцию или заполнить поле ввода из другого взаимодействия. Ни то ни другое не отправляет событие; предзаполненное поле ввода сообщается только после того, как пользователь его отредактирует.

## Что вы получаете \{#what-you-receive\}

Коллбэк доставляет два типа событий. Фильтруйте по `name`, чтобы отображать только события `flow_user_input`. JSON-ответ выглядит так:

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

| Параметр          | Описание |
|:------------------|:---------|
| `name`            | `flow_user_input` для событий ввода, `flow_screen_showed` для просмотров экрана. |
| `instanceId`      | ID экрана, на котором находится поле ввода. ID элементов уникальны в пределах экрана, но не в рамках всего флоу. Если в вашем флоу есть поля ввода на нескольких экранах, используйте `instanceId` вместе с `element_id` при фильтрации событий. |
| `element_id`      | **Element ID** [поля ввода](builder-inputs-and-forms) или **Group ID** [группы выбора](flow-selectable-elements). |
| `element_type`    | Тип элемента, отправившего событие. Определяет, в каком из параметров ниже хранится значение ввода. |
| `value`           | **Только для текстовых полей, пикеров и переключателей.** Значение ввода: строка для текстовых полей, целое число для пикеров, булево значение для переключателей. |
| `item_ids`        | **Только для групп с единственным и множественным выбором.** **Element ID** выбранных вариантов в порядке их отображения в билдере. Один элемент для группы с единственным выбором; любое количество для группы с множественным выбором. |
| `item_titles`     | **Только для групп с единственным и множественным выбором.** Заголовки вариантов, перечисленных в `item_ids`, в том же порядке. Никогда не пустой: вариант без заголовка возвращает свой ID. |
| `isCustomerEvent` | Служебный флаг, всегда `true` для этого события. Помечает события, которые SDK доставляет в ваш коллбэк. Удобен, если один обработчик пересылает все события флоу в вашу аналитику, а вы фильтруете по этому флагу, а не по `name`. |
| `isBackendEvent`  | Служебный флаг, всегда `false` для этого события. Помечает события, которые Adapty также записывает для собственной аналитики. Значение `false` подтверждает, что введённые пользователями данные поступают только в ваше приложение — Adapty их не получает и не хранит. |

Что передаёт каждый элемент:

| В билдере | `element_type` | Параметр со значением | Что содержит |
|:----------|:---------------|:----------------------|:-------------|
| Ввод **Text**, **Number**, **Phone number** | `text_input`, `number_input`, `phone_input` | `value` | Строка, введённая пользователем. Числа передаются как строки, не как числовые типы. |
| Ввод **E-mail** | `email_input` | `value` | Строка, введённая пользователем, даже если она не прошла проверку формата в билдере. Валидируйте её на своей стороне перед использованием. |
| Ввод **Password** | нет | нет | Не отправляет событий. |
| Ввод **Date** | `date_picker` | `value` | Unix-время в миллисекундах, целое число, соответствующее полуночи выбранной даты по местному времени. |
| Ввод **Time** | `time_picker` | `value` | Unix-время в миллисекундах, целое число, округлённое до минуты. |
| Ввод **Date & Time** | `date_picker` и `time_picker` | `value` | Два элемента: пикер даты и пикер времени. Каждый отправляет собственное событие. |
| Ввод с переключением на **Date & Time** в выпадающем списке **Type** | `date_time_picker` | `value` | Unix-время в миллисекундах, целое число, округлённое до минуты. |
| Группа **Single choice** | `single_choice` | `item_ids`, `item_titles` | Два массива. `item_ids`: массив с Element ID выбранного варианта. `item_titles`: массив с заголовком этого варианта. |
| Группа **Multi-choice** | `multi_choice` | `item_ids`, `item_titles` | Два массива. `item_ids`: Element ID всех выбранных вариантов в порядке их отображения в билдере. `item_titles`: их заголовки в том же порядке. Оба массива пусты, если ничего не выбрано. |
| Группа **Toggle** | `toggle` | `value` | Булево значение. |

Для ветвления по ответу сравнивайте `item_ids`, а не `item_titles`. Заголовок — производная величина: **Element Title** варианта, если он задан; иначе его текст в вашей локали по умолчанию; иначе его Element ID. Пользователь, просматривавший флоу на другом языке, видел другой текст.

## Примеры событий \{#event-examples\}

Эти примеры показывают свойства, доступные для каждого события, с иллюстративными значениями в комментариях.

<Details>
<summary>Текстовый, email, числовой и телефонный ввод (нажмите, чтобы развернуть)</summary>

```kotlin
override fun onAnalyticEvent(name: String, params: Map<String, Any?>, context: Context) {
    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)
}
```
</Details>

<Details>
<summary>Выбор даты, времени и даты со временем (нажмите, чтобы развернуть)</summary>

```kotlin
override fun onAnalyticEvent(name: String, params: Map<String, Any?>, context: Context) {
    params["element_id"]       // "birthday"
    params["element_type"]     // "date_picker"
    params["value"]            // 6.45408E11   (Double — Unix milliseconds, 1990-06-15, local midnight)
}
```
</Details>

<Details>
<summary>Single choice (Click to expand)</summary>

```kotlin
override fun onAnalyticEvent(name: String, params: Map<String, Any?>, context: Context) {
    params["element_id"]       // "experience"
    params["element_type"]     // "single_choice"
    params["item_ids"]         // ["pro"]   (List<String>)
    params["item_titles"]      // ["I train professionally"]
}
```
</Details>

<Details>
<summary>Множественный выбор (нажмите, чтобы развернуть)</summary>

```kotlin
override fun onAnalyticEvent(name: String, params: Map<String, Any?>, context: Context) {
    params["element_id"]       // "interests"
    params["element_type"]     // "multi_choice"
    params["item_ids"]         // ["sports", "music"]   (List<String>)
    params["item_titles"]      // ["Sports", "Music"]
}
```
</Details>

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

```kotlin
override fun onAnalyticEvent(name: String, params: Map<String, Any?>, context: Context) {
    params["element_id"]       // "reminders"
    params["element_type"]     // "toggle"
    params["value"]            // true   (Boolean)
}
```
</Details>

## Доставка и ограничения \{#delivery-and-limitations\}

:::warning
Флоу передают необработанные значения — адреса электронной почты, номера телефонов и всё, что вводит пользователь. Относитесь ко всему, что приходит в аналитический колбэк, как к персональным данным, и не записывайте это туда, куда бы вы не записали email пользователя.
:::

- **Побеждает последнее значение**: вы получаете одно событие на поле с тем значением, на котором пользователь остановился, а не поток каждого нажатия клавиши. Если он редактирует поле, до вас дойдёт только последняя версия.
- **Нет гарантии отправки**: значения поступают к вам по мере того, как пользователь движется по флоу, и он может выйти в любой момент. Дождитесь закрытия флоу, прежде чем считать набор ответов завершённым.
- **Доставка по возможности**: если пользователь закрывает флоу или сворачивает приложение, пока поле ещё в фокусе или пикер открыт, это значение может быть потеряно.

Чтобы значение последнего поля было надёжным, завершайте флоу экраном без полей ввода и пикеров, и закрывайте флоу явным действием пользователя, а не автоматически при появлении этого экрана. Переход на финальный экран снимает фокус с предыдущего поля — именно это и отправляет его значение.

Сохраняйте каждое входное значение по мере того, как его получает ваш слушатель, и отправляйте полный набор, когда флоу закрывается. Чтобы поймать этот момент, переопределите `onFlowClosed` в вашем `AdaptyFlowEventListener`. Метод срабатывает при закрытии представления флоу — независимо от того, завершил ли пользователь флоу полностью или закрыл его на середине.

## Примеры использования \{#use-cases\}

### Регистрируйте пользователей на своём бэкенде \{#register-users-on-your-backend\}

Собирайте значения по мере их поступления и отправляйте их одним запросом после того, как флоу закроется — так один запрос будет содержать полный набор ответов.

Флоу закрывается как при завершении, так и при частичном выходе из него. Перед вызовом бэкенда проверяйте наличие нужных полей.

Флоу не может отображать ошибки от вашего бэкенда. Коллбэк не возвращает никакого значения, и в SDK нет метода для передачи данных в уже запущенный флоу. Если регистрация завершилась ошибкой — например, потому что email уже используется — покажите её в собственном интерфейсе после закрытия флоу.

```kotlin showLineNumbers title="Kotlin"
class MyFlowEventListener : AdaptyFlowDefaultEventListener() {

    private val flowAnswers = mutableMapOf<String, String>()

    override fun onAnalyticEvent(
        name: String,
        params: Map<String, Any?>,
        context: Context,
    ) {
        if (name != "flow_user_input") return

        val elementId = params["element_id"] as? String ?: return
        val value = params["value"] as? String ?: return

        flowAnswers[elementId] = value
    }

    override fun onFlowClosed() {
        if (!flowAnswers.containsKey("email")) return

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

        flowAnswers.clear()
    }
}
```

### Обогащайте профили пользователей данными \{#enrich-user-profiles-with-data\}

Чтобы связать введённые пользователем данные с его профилем и не запрашивать одно и то же дважды, [обновляйте профиль пользователя](android-setting-user-attributes) по мере поступления значений.

Например, если в вашем флоу есть текстовое поле с идентификатором элемента `name` и поле email с идентификатором `email`:

```kotlin showLineNumbers title="Kotlin"
private fun handleFlowInput(name: String, params: Map<String, Any?>) {
    if (name != "flow_user_input") return

    val elementId = params["element_id"] as? String ?: return
    val value = params["value"] as? String ?: return

    val builder = AdaptyProfileParameters.Builder()

    when (elementId) {
        "name" -> builder.withFirstName(value)
        "email" -> builder.withEmail(value)
        else -> return
    }

    Adapty.updateProfile(builder.build()) { error ->
        if (error != null) {
            // handle the error
        }
    }
}
```

### Настройка флоу, которые показываются позже \{#customize-flows-shown-later\}

Ответы на вопросы квиза также могут влиять на то, что пользователь увидит в другом [плейсменте](placements) — другой флоу или другой пейвол внутри него.

Например, в онбординг-флоу спросите пользователей об их опыте в спорте, а затем покажите каждой группе свой флоу с разными продуктами и текстами.

1. Добавьте [квиз](onboarding-quizzes) в свой флоу. Присвойте [выбираемой группе](flow-selectable-elements) Group ID `experience`, а каждому варианту — понятный Element ID.
2. Обработайте ответы и [задайте кастомные атрибуты](android-setting-user-attributes) для пользователя.

```kotlin showLineNumbers title="Kotlin"
private fun handleFlowInput(name: String, params: Map<String, Any?>) {
    if (name != "flow_user_input") return
    if (params["element_id"] != "experience") return

    val optionId = (params["item_ids"] as? List<*>)?.firstOrNull() as? String ?: return

    val builder = AdaptyProfileParameters.Builder()
    // Set the custom attribute 'experience' to the option the user selected
    // (beginner, amateur, or pro).
    builder.withCustomAttribute("experience", optionId)

    Adapty.updateProfile(builder.build()) { error ->
        if (error != null) {
            // handle the error
        }
    }
}
```

3. [Создайте сегмент](segments) для каждого значения кастомного атрибута.
4. Создайте [плейсмент](placements) и добавьте [аудиторию](audience) для каждого сегмента.
5. [Отобразите флоу](android-present-paywalls) для этого плейсмента в своём приложении.