---
title: "在 Kotlin Multiplatform SDK 中向流程传递数据"
description: "将 Kotlin Multiplatform 应用中的值传递给流程，并在文本元素中显示。"
---

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

[自定义标签](flow-custom-tags)是一种自定义变量，其值来自你的应用。在变量设置中选择 **Value comes from outside the app** 后，你的应用就可以通过 Adapty SDK 设置该变量的值。例如，一个文本元素包含"Coins:"和一个自定义标签。如果你的应用为该标签传入"120"，用户看到的就是"Coins: 120"。

## 开始之前 \{#before-you-start\}

您需要使用 Adapty SDK v4 或更高版本。v4 之前的版本不支持显示流程。

## 向流程添加自定义标签 \{#add-custom-tags-to-the-flow\}

要在流程中显示应用的数据，请在编辑工具中设置自定义标签：

1. 为应用传递的每个值[创建一个自定义标签](flow-custom-tags#create-a-custom-tag)。

   <ZoomImage id="flow-custom-tag-create.webp" width="500px" alt="Create variable form with a name, a description, the String value type, and an initial value" />

2. [将每个标签插入文本元素](flow-custom-tags#add-a-custom-tag-to-a-text-element)。
3. 记录每个标签的完整名称，包括其所属分组。应用代码在传递对应值时需使用该名称。例如，若 `user` 分组中包含 `streak` 标签，则该标签的完整名称为 `user.streak`。

   <ZoomImage id="flow-custom-tag-full-name.webp" width="500px" alt="Custom tab of the Add variable dropdown with the streak tag in the user group" />

## 传递自定义标签值 \{#pass-custom-tag-values\}

将应用中的值通过 `customTags` 参数传入 `AdaptyUI.createFlowView` 或 `createNativeFlowView`，格式为 `Map<String, String>`：

- 每个键是自定义标签的完整名称，例如 `user.streak`。
- 每个值是用户在标签位置实际看到的字符串。
- 每个值必须是人类可读的格式，例如 "1,250"，而非 "1250.0"。Adapty 不会对其进行格式化或翻译。

SDK 在创建视图时读取一次这些值，因此在流程打开期间不会发生变化。

<Tabs>
<TabItem value="standalone" label="With Compose Multiplatform" default>

```kotlin showLineNumbers title="Kotlin Multiplatform"

viewModelScope.launch {
    val customTags = mapOf(
        "username" to "DragonSlayer42",
        "coins" to "1,250",
    )

    AdaptyUI.createFlowView(
        flow = flow,
        customTags = customTags,
    ).onSuccess { view ->
        view.present()
    }.onError { error ->
        // handle the error
    }
}
```

要在 Compose 布局中嵌入流程，请将相同的 `customTags` 参数传递给 `AdaptyUIFlowPlatformView` 可组合项。如果传入不同的 `customTags` 映射，可组合项将创建一个新视图，用户将返回流程的第一屏。

</TabItem>
<TabItem value="native" label="Without Compose Multiplatform">

```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"

val customTags = mapOf(
    "username" to "DragonSlayer42",
    "coins" to "1,250",
)

val nativeView = AdaptyUI.createNativeFlowView(
    context = context,
    viewModelStoreOwner = activity,
    flow = flow,
    observer = myFlowObserver,
    customTags = customTags,
)
```

```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)"

val customTags = mapOf(
    "username" to "DragonSlayer42",
    "coins" to "1,250",
)

val nativeView = AdaptyUI.createNativeFlowView(
    flow = flow,
    observer = myFlowObserver,
    customTags = customTags,
)
```

</TabItem>
</Tabs>

要测试自定义标签，请在添加设置其值的代码后，在应用中打开流程。编辑工具画布和[在设备上预览](paywall-device-compatibility-preview#preview-on-devices)只显示初始值：Adapty 移动应用不会设置自定义标签的值。

如果您的应用已经为旧版付费墙编辑工具的付费墙设置了自定义标签值，请查阅[流程中的自定义标签与旧版的区别](migrate-to-flows#custom-tags-from-the-old-paywall-builder)。

## 用户在没有值时看到的内容 \{#what-users-see-without-a-value\}

在以下情况下，用户会看到自定义标签的**初始值**作为备用内容：

- 您的应用未为该标签传递任何值。
- 用户尚未将应用更新到能传递该值的版本。
- 应用代码中的名称与标签的完整名称不匹配，包括字母大小写。
- 同一文本元素中的另一个自定义标签没有值。此时该文本元素中的每个自定义标签都会显示其初始值。

如需在标签位置不显示任何内容，请传入空字符串。Adapty 将空字符串视为有效值，因此不会显示备用内容。

## 示例：根据用户引导答案显示 BMI \{#example-show-a-bmi-from-onboarding-answers\}

流程可以收集用户的答案，自定义标签可以显示来自你应用的值。将两者结合，即可向用户展示由你的应用根据其答案计算得出的结果，例如得分、推荐套餐或健康指标：

1. 流程收集答案并将其发送给你的应用。
2. 你的应用计算结果。
3. 你的应用将结果作为自定义标签值传递给下一个流程。

在此示例中，一个用户引导流程收集用户的体重和身高。当用户在最后一屏关闭用户引导流程后，你的应用会计算其身体质量指数（BMI）。然后它会打开一个付费墙流程，显示该 BMI 值。

### 设置流程 \{#set-up-the-flows\}

1. [创建两个流程](paywall-builder-templates)：一个用于收集用户体重和身高的用户引导流程，以及一个展示 BMI 的付费墙流程。
2. 在用户引导流程中添加两个 [**Number** 输入框](builder-inputs-and-forms)：
   - 一个用于输入体重（千克），**Element ID** 设为 `weight`。
   - 一个用于输入身高（厘米），**Element ID** 设为 `height`。

   两个输入框的 **Format** 均设为 **Integer**。输入框会将用户输入的内容原样以文本形式发送，[示例代码](#pass-the-bmi-to-the-paywall)对 "70.5" 和 "70,5" 的解析结果并不相同。整数没有小数分隔符，因此在任何语言环境下代码的解析结果都一致。
3. 为确保您的应用能收到两个值，请在用户引导流程的最后添加一个不含输入框的屏幕。在该屏幕上添加一个带有 [**Close flow**](onboarding-actions#close-flow) 操作的按钮。输入框会在失去焦点或用户切换到其他屏幕时发送其值，而 Close flow 操作不会触发发送。示例代码只有在用户到达最后一屏时才会打开付费墙。
4. 在付费墙流程中，[创建一个名为 `bmi` 的自定义标签](flow-custom-tags#create-a-custom-tag)。在 **Initial value** 字段中输入一个破折号（`—`）。如果您的应用未传入 BMI 值，用户将看到该破折号。
5. 在付费墙流程中添加一个文本元素，内容为"Your BMI:"。在这些文字后面[插入 `bmi` 自定义标签](flow-custom-tags#add-a-custom-tag-to-a-text-element)。
6. 将用户引导流程添加到某个[版位](placements)，例如 `onboarding`。将付费墙流程添加到 ID 为 `bmi_paywall` 的版位。[将 BMI 传递给付费墙](#pass-the-bmi-to-the-paywall)中的代码通过此 ID 获取付费墙。

### 将 BMI 传递给付费墙 \{#pass-the-bmi-to-the-paywall\}

从用户引导流程的 `flow_user_input` 事件中收集体重和身高数据，具体方法参见[处理流程中的数据](kmp-flow-input)。要判断用户是否到达最后一个屏幕，可监听 `flow_screen_showed` 事件中 `is_last_screen` 为 `true` 的情况，具体方法参见[追踪流程屏幕浏览](kmp-flow-screen-views)。当用户在该屏幕后关闭用户引导流程时，计算 BMI，然后创建付费墙视图，并将 BMI 传入 `bmi` 自定义标签。

`flowViewDidReceiveAnalyticEvent` 方法使用 `kotlinx.serialization.json` 库解析事件参数。如果你的项目中还没有该库，请将 `org.jetbrains.kotlinx:kotlinx-serialization-json` 添加到应用的依赖项中。然后在通过 `AdaptyUI.setFlowsEventsObserver` 注册的观察者上实现以下方法：

```kotlin showLineNumbers title="Kotlin Multiplatform"

class MyFlowsEventsObserver(
    private val scope: CoroutineScope,
) : AdaptyUIFlowsEventsObserver {

    private val bodyAnswers = mutableMapOf<String, String>()
    private var reachedLastScreen = false

    override fun flowViewDidReceiveAnalyticEvent(
        view: AdaptyUIFlowView,
        name: String,
        paramsJsonString: String,
    ) {
        if (name != "flow_user_input" && name != "flow_screen_showed") return

        val params = Json.parseToJsonElement(paramsJsonString).jsonObject
        if (name == "flow_screen_showed") {
            if (params["is_last_screen"]?.jsonPrimitive?.booleanOrNull == true) {
                reachedLastScreen = true
            }
            return
        }
        val elementId = params["element_id"]?.jsonPrimitive?.content ?: return
        val value = params["value"]?.jsonPrimitive?.contentOrNull ?: return

        bodyAnswers[elementId] = value
    }

    override fun flowViewDidDisappear(view: AdaptyUIFlowView) {
        // The observer receives events from every flow. Reset `bodyAnswers` and
        // `reachedLastScreen` whenever a flow disappears, so that the paywall
        // doesn't open again when the user closes it.
        val answers = bodyAnswers.toMap()
        val finished = reachedLastScreen
        bodyAnswers.clear()
        reachedLastScreen = false

        // Number inputs send their values as strings.
        if (!finished) return
        val weight = answers["weight"]?.toDoubleOrNull() ?: return
        val height = answers["height"]?.toDoubleOrNull()?.takeIf { it > 0 } ?: return

        val heightInMeters = height / 100
        val bmi = weight / (heightInMeters * heightInMeters)
        val bmiText = ((bmi * 10).roundToInt() / 10.0).toString()

        scope.launch { showBmiPaywall(bmiText) }
    }
}

suspend fun showBmiPaywall(bmi: String) {
    Adapty.getFlow(placementId = "bmi_paywall").onSuccess { flow ->
        AdaptyUI.createFlowView(
            flow = flow,
            customTags = mapOf("bmi" to bmi),
        ).onSuccess { view ->
            view.present()
        }.onError { error ->
            // handle the error
        }
    }.onError { error ->
        // handle the error
    }
}
```

`toString` 始终使用句点作为小数分隔符，例如 "22.7"。若要使用用户所在语言区域的分隔符，请在各平台上使用支持本地化的格式化工具对 BMI 进行格式化，例如 Android 上的 `java.text.NumberFormat` 和 iOS 上的 `NSNumberFormatter`。

### 将 BMI 保存到用户画像

要根据用户的 BMI 展示不同的流程，需将其作为用户画像的[自定义属性](kmp-setting-user-attributes#custom-user-attributes)保存。在 Adapty 看板中，[创建一个键名为 `bmi` 的 **Number** 类型自定义属性](segments#custom-attributes)，然后将每个流程展示给基于该属性构建的[目标受众](audience)。

在 `flowViewDidDisappear` 中调用 `updateProfile` 方法，紧跟在计算 `bmi` 的那一行之后。传入 `Double` 类型的原始值，而非格式化后的字符串，这样市场细分才能将 BMI 与阈值进行比较：

```kotlin showLineNumbers title="Kotlin Multiplatform"

val params = AdaptyProfileParameters.Builder()
    .withCustomAttribute("bmi", bmi)
    .build()

scope.launch {
    Adapty.updateProfile(params).onError { error ->
        // handle the error
    }
}
```