---
title: "在 Unity SDK 中处理流程数据"
description: "使用 Adapty SDK 保存并使用用户在流程中输入的数据，适用于 Unity 应用。"
---

> **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 会通过分析回调将该值传递给你的应用。

应用通常使用这些数据来：

- **在自有后端注册用户**：获取用户在引导流程中填写的邮箱和姓名，在流程关闭时为其创建账号。
- **保存答案和偏好设置**：记录用户的选择，以便应用后续使用——例如，[将其作为自定义属性写入用户的 Adapty 用户画像](unity-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` 的形式传递到同一个回调中。在通过 `Adapty.SetFlowsEventsListener` 注册的监听器上实现 `FlowViewDidReceiveAnalyticEvent`：

```csharp showLineNumbers title="Unity"
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    HandleFlowInput(name, parameters);
}
```

`FlowViewDidReceiveAnalyticEvent` 回调会传递流程中的所有分析事件，包括[页面浏览事件](unity-flow-screen-views)。
- `name` 参数包含事件名称。若要筛选用户输入事件，可将 `name` 与 `flow_user_input` 进行比较。
- `element_type` 参数表示元素类别。
- 输入的值根据元素类型存储在不同参数中：
     - 文本框、选择器和开关将用户输入存储在 `value` 中
     - 可选分组在 `item_ids` 和 `item_titles` 中报告已激活的选项

```csharp showLineNumbers title="Unity"
private void HandleFlowInput(string name, IReadOnlyDictionary<string, object> parameters) {
    if (name != "flow_user_input") return;

    var elementId = parameters["element_id"] as string;

    // The screen the input sits on. Pair it with elementId to tell apart
    // two fields that share an Element ID on different screens.
    var screenId = parameters["instanceId"] as string;

    switch (parameters["element_type"] as string) {
        case "text_input":
        case "email_input":
        case "number_input":
        case "phone_input":
            var text = parameters["value"] as string;
            break;
        case "date_picker":
        case "time_picker":
        case "date_time_picker":
            // Unix time in milliseconds.
            var millis = Convert.ToInt64(parameters["value"]);
            break;
        case "single_choice":
            var optionId = (parameters["item_ids"] as JArray)?.First?.ToString();
            break;
        case "multi_choice":
            var optionIds = (parameters["item_ids"] as JArray)?.ToObject<List<string>>();
            break;
        case "toggle":
            var isOn = (bool)parameters["value"];
            break;
    }
}
```

要确认回调是否触发，请在你自己的应用测试版本中与输入进行交互。如果你的回调处理程序未收到事件，请检查[前提条件](#before-you-start)。请确保该流程是在此功能可用之后才发布的。

## 应用接收输入的时机 \{#when-your-app-receives-the-input\}

:::important
默认输入值或选中值不会通过此回调传递到你的应用。如果用户接受了标记为 **Set as default** 的选项并继续操作，不会触发任何事件。不要把缺少事件理解为"未作答"——用户只是保留了默认值。
:::

**以下元素会触发此事件：**

- 文本、电子邮件、数字和电话输入框
- 日期、时间和日期时间选择器
- 单选和多选组以及开关

**以下元素不会触发：**

- 密码输入框、产品选择器和标签切换
- 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` | [输入框](builder-inputs-and-forms)的 **Element ID**，或[可选择分组](flow-selectable-elements)的 **Group ID**。 |
| `element_type` | 发送事件的元素类型，决定以下哪个参数携带输入值。 |
| `value` | **仅适用于文本字段、选择器和开关。** 输入值：文本字段为字符串，选择器为整数，开关为布尔值。 |
| `item_ids` | **仅适用于单选和多选分组。** 已选项的 **Element ID**，顺序与编辑工具中选项的排列顺序一致。单选组有一个条目，多选组可有任意数量。 |
| `item_titles` | **仅适用于单选和多选分组。** `item_ids` 中各选项对应的标题，顺序相同。该字段不会为空：没有标题的选项会上报其 ID。 |
| `isCustomerEvent` | 工具标志位，此事件始终为 `true`。用于标记 SDK 传递给回调的事件。如果一个处理器将所有流程事件转发至埋点系统，可通过此标志而非 `name` 进行过滤。 |
| `isBackendEvent` | 工具标志位，此事件始终为 `false`。用于标记 Adapty 同步记录至自身分析系统的事件。`false` 表示用户输入的内容仅到达你的应用，不会发送到其他地方——Adapty 不会接收或存储这些数据。 |

各元素上报内容说明：

| 编辑工具中的元素 | `element_type` | 存储值的参数 | 内容说明 |
|:---------------|:---------------|:----------------------------|:--------------|
| **文本**、**数字**、**电话号码**输入框 | `text_input`、`number_input`、`phone_input` | `value` | 用户输入的原始字符串。数字以字符串形式传递，不是数值类型。 |
| **邮箱**输入框 | `email_input` | `value` | 用户输入的原始字符串，即使未通过编辑工具的格式校验也会上报。使用前请在你的一侧进行验证。 |
| **密码**输入框 | 无 | 无 | 不发送任何事件。 |
| **日期**输入框 | `date_picker` | `value` | 所选日期本地零点的 Unix 时间戳（毫秒），整数类型。 |
| **时间**输入框 | `time_picker` | `value` | 精确到分钟的 Unix 时间戳（毫秒），整数类型。 |
| **日期 & 时间**输入框 | `date_picker` 和 `time_picker` | `value` | 包含两个元素：日期选择器和时间选择器，各自发送独立事件。 |
| 在 **Type** 下拉菜单中切换为 **Date & Time** 的输入框 | `date_time_picker` | `value` | 精确到分钟的 Unix 时间戳（毫秒），整数类型。 |
| **单选**分组 | `single_choice` | `item_ids`、`item_titles` | 两个数组。`item_ids`：包含已选项 Element ID 的数组。`item_titles`：包含该选项标题的数组。 |
| **多选**分组 | `multi_choice` | `item_ids`、`item_titles` | 两个数组。`item_ids`：所有已选项的 Element ID，顺序与编辑工具中的排列一致。`item_titles`：对应的标题，顺序相同。未选中任何选项时两个数组均为空。 |
| **开关**分组 | `toggle` | `value` | 布尔值。 |

根据答案进行分支判断时，请比较 `item_ids`，而非 `item_titles`。标题是派生值：优先使用选项的 **Element Title**（如已设置），其次是默认语言环境中的文本，最后是 Element ID。使用其他语言查看流程的用户看到的文本可能不同。

## 事件示例 \{#event-examples\}

以下示例展示了每个事件可用的属性及其示意值（见注释）。

<Details>
<summary>文本、邮箱、数字和手机号输入（点击展开）</summary>

```csharp
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    var eventName       = name;                          // "flow_user_input"
    var paramName       = parameters["name"];            // "flow_user_input"
    var screenId        = parameters["instanceId"];      // "scr_J260KU5q"
    var isCustomerEvent = parameters["isCustomerEvent"]; // true
    var isBackendEvent  = parameters["isBackendEvent"];  // false
    var elementId       = parameters["element_id"];      // "email"
    var elementType     = parameters["element_type"];    // "email_input"
    var value           = parameters["value"];           // "jane@example.com"   (string)
}
```
</Details>

<Details>
<summary>日期、时间和日期时间选择器（点击展开）</summary>

```csharp
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    var elementId   = parameters["element_id"];   // "birthday"
    var elementType = parameters["element_type"]; // "date_picker"
    var value       = parameters["value"];        // 645408000000   (long — Unix milliseconds, 1990-06-15, local midnight)
}
```
</Details>

<Details>
<summary>单选（点击展开）</summary>

```csharp
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    var elementId   = parameters["element_id"];   // "experience"
    var elementType = parameters["element_type"]; // "single_choice"
    var itemIds     = parameters["item_ids"];     // ["pro"]   (JArray)
    var itemTitles  = parameters["item_titles"];  // ["I train professionally"]
}
```
</Details>

<Details>
<summary>多选（点击展开）</summary>

```csharp
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    var elementId   = parameters["element_id"];   // "interests"
    var elementType = parameters["element_type"]; // "multi_choice"
    var itemIds     = parameters["item_ids"];     // ["sports", "music"]   (JArray)
    var itemTitles  = parameters["item_titles"];  // ["Sports", "Music"]
}
```
</Details>

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

```csharp
public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    var elementId   = parameters["element_id"];   // "reminders"
    var elementType = parameters["element_type"]; // "toggle"
    var value       = parameters["value"];        // true   (bool)
}
```
</Details>

## 传递方式与限制 \{#delivery-and-limitations\}

:::warning
流程会发送原始值——电子邮件地址、电话号码以及用户输入的任何其他内容。请将分析回调传递的所有内容视为个人数据，不要将其写入任何你不会写入用户电子邮件地址的地方。
:::

- **后值覆盖前值**：每个字段只触发一个事件，携带的是用户最终确认的值，而非逐键输入的流。如果用户编辑了某个字段，只有最后一个版本会传递给你。
- **无提交保证**：值会在用户浏览流程时传递给你，用户可以在任意时刻离开。在将一组答案视为完整之前，请等待流程关闭。
- **尽力传递**：如果用户在某个字段仍处于焦点状态或选择器仍处于打开状态时关闭流程或将应用切换到后台，该值可能会丢失。

为确保最后一个字段的值可靠传递，请以一个没有输入框和选择器的屏幕结束流程，并通过用户的明确操作（而非该屏幕出现时自动）关闭流程。跳转到最终屏幕会使前一个字段失去焦点，从而触发其值的发送。

每次监听器收到输入值时请及时保存，并在流程关闭时一并发送完整数据。要捕获该时机，请在同一监听器上实现 `FlowViewDidDisappear`。无论用户是完成了整个流程还是中途关闭，该方法都会在流程视图消失时触发。

## 使用场景 \{#use-cases\}

### 在您的后端注册用户 \{#register-users-on-your-backend\}

在答案陆续收到时暂存数据，等流程关闭后再统一发送，这样一次请求就能携带完整的答案集合。

无论用户是完成了流程还是中途退出，视图都会消失。在调用后端之前，请检查所需字段是否已填写。

流程无法展示来自后端的错误。回调没有返回值，SDK 也没有办法向正在运行的流程传递数据。如果注册失败（例如邮箱已被占用），请在流程关闭后通过您自己的 UI 展示错误信息。

```csharp showLineNumbers title="Unity"
private readonly Dictionary<string, string> flowAnswers = new Dictionary<string, string>();

public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IReadOnlyDictionary<string, object> parameters
) {
    if (name != "flow_user_input") return;
    if (!parameters.TryGetValue("value", out var raw) || !(raw is string value)) return;

    var elementId = parameters["element_id"] as string;
    flowAnswers[elementId] = value;
}

public void FlowViewDidDisappear(AdaptyUIFlowView view) {
    if (!flowAnswers.ContainsKey("email")) return;

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

    flowAnswers.Clear();
}
```

### 用户画像数据填充 \{#enrich-user-profiles-with-data\}

要将用户输入的内容关联到其用户画像，并避免重复询问相同信息，请在数据输入时[更新用户画像](unity-setting-user-attributes)。

例如，如果你的流程中有一个 Element ID 为 `name` 的文本输入框和一个 Element ID 为 `email` 的邮箱输入框：

```csharp showLineNumbers title="Unity"
private void HandleFlowInput(string name, IReadOnlyDictionary<string, object> parameters) {
    if (name != "flow_user_input") return;
    if (!parameters.TryGetValue("value", out var raw) || !(raw is string value)) return;

    var builder = new AdaptyProfileParameters.Builder();

    switch (parameters["element_id"] as string) {
        case "name":
            builder = builder.SetFirstName(value);
            break;
        case "email":
            builder = builder.SetEmail(value);
            break;
        default:
            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. 处理答案并为用户[设置自定义属性](unity-setting-user-attributes)。

```csharp showLineNumbers title="Unity"
private void HandleFlowInput(string name, IReadOnlyDictionary<string, object> parameters) {
    if (name != "flow_user_input") return;
    if ((parameters["element_id"] as string) != "experience") return;

    var optionId = (parameters["item_ids"] as JArray)?.First?.ToString();
    if (optionId == null) return;

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

    Adapty.UpdateProfile(builder.Build(), (error) => {
        if (error != null) {
            // handle the error
        }
    });
}
```

3. 为每个自定义属性值[创建市场细分](segments)。
4. 创建[版位](placements)，并为每个市场细分添加[目标受众](audience)。
5. 在你的应用中[展示该版位的流程](unity-present-paywalls)。