Передача данных во флоу в Kotlin Multiplatform SDK

Кастомный тег — это кастомная переменная со значением из вашего приложения. После того как вы выберете Value comes from outside the app в настройках переменной, приложение сможет задать её значение через Adapty SDK. Например, текстовый элемент содержит «Coins:» и кастомный тег. Если приложение передаёт «120» для этого тега, пользователи видят «Coins: 120».

Прежде чем начать

Вам необходим Adapty SDK версии 4 или выше. Версии ниже v4 не отображают флоу.

Добавьте пользовательские теги во флоу

Чтобы отображать значения из вашего приложения во флоу, настройте кастомные теги в билдере:

  1. Создайте кастомный тег для каждого значения, которое передаёт ваше приложение.

    Форма создания переменной с именем, описанием, типом значения String и начальным значением
  2. Вставьте каждый тег в текстовый элемент.

  3. Запишите полное имя каждого тега, включая его группу. Именно под этим именем код вашего приложения передаёт каждое значение. Например, если группа user содержит тег streak, полное имя тега — user.streak.

    Вкладка Custom в выпадающем списке Add variable с тегом streak в группе user

Передача значений кастомных тегов

Передайте значения вашего приложения в параметр customTags метода AdaptyUI.createFlowView или createNativeFlowView в виде Map<String, String>:

  • Каждый ключ — это полное имя кастомного тега, например user.streak.
  • Каждое значение — строка, которую видит пользователь вместо тега.
  • Значения должны быть удобочитаемыми, например “1,250”, а не “1250.0”. Adapty не форматирует и не переводит их.

SDK считывает значения один раз — при создании представления, поэтому они не изменяются пока флоу открыт.

Чтобы протестировать пользовательские теги, откройте флоу в своём приложении после добавления кода, который устанавливает их значения. Холст конструктора и предпросмотр на устройствах отображают только начальные значения: мобильное приложение Adapty не устанавливает значения пользовательских тегов.

Если ваше приложение уже устанавливает значения custom tags для старых пейволов Paywall Builder, ознакомьтесь с тем, чем custom tags во флоу отличаются от старых.

Что видят пользователи, если значение отсутствует

Пользователи видят Начальное значение пользовательского тега как резервное в следующих случаях:

  • Ваше приложение не передаёт никакого значения для тега.
  • Пользователь не обновил приложение до версии, которая передаёт это значение.
  • Имя в коде вашего приложения не совпадает с полным именем тега, включая регистр букв.
  • Другой пользовательский тег в том же текстовом элементе не имеет значения. В этом случае каждый пользовательский тег в этом текстовом элементе показывает своё начальное значение.

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

Пример: отображение ИМТ по ответам из онбординга

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

  1. Флоу собирает ответы и передаёт их в ваше приложение.
  2. Приложение вычисляет результат.
  3. Приложение передаёт результат в следующий флоу в виде значения кастомного тега.

В этом примере онбординг-флоу собирает вес и рост пользователя. Когда пользователь закрывает онбординг-флоу после последнего экрана, приложение вычисляет индекс массы тела (ИМТ). Затем открывается пейвол-флоу, который показывает этот ИМТ.

Настройка флоу

  1. Создайте два флоу: онбординг-флоу, собирающий вес и рост пользователя, и флоу-пейвол, показывающий его ИМТ.

  2. Добавьте два поля Number в онбординг-флоу:

    • Одно для веса в килограммах с Element ID weight.
    • Одно для роста в сантиметрах с Element ID height.

    Для обоих полей установите Format в значение Integer. Поле отправляет значение как текст, точно так, как его ввёл пользователь, а пример кода не воспринимает «70.5» и «70,5» одинаково. Целые числа не содержат разделителя дробной части, поэтому код читает их одинаково в любой локали.

  3. Чтобы убедиться, что приложение получает оба значения, завершите онбординг-флоу экраном без полей ввода. На этом экране добавьте кнопку с действием Close flow. Поля отправляют свои значения при потере фокуса или при переходе пользователя на другой экран. Действие Close flow не отправляет их. Пример кода открывает пейвол только в том случае, если пользователь добрался до последнего экрана.

  4. В флоу-пейволе создайте кастомный тег с именем bmi. В поле Initial value введите тире (—). Пользователь увидит тире, если приложение не передало значение ИМТ.

  5. Добавьте текстовый элемент со словами «Your BMI:» в флоу-пейвол. После этих слов вставьте кастомный тег bmi.

  6. Добавьте онбординг-флоу в плейсмент, например onboarding. Добавьте флоу-пейвол в плейсмент с ID bmi_paywall. Код из раздела Pass the BMI to the paywall получает пейвол по этому ID.

Передача ИМТ в пейвол

Получите вес и рост из событий flow_user_input онбординг-флоу, как описано в разделе Обработка данных из флоу. Чтобы определить момент достижения последнего экрана, отслеживайте событие flow_screen_showed с is_last_screen равным true, как описано в разделе Отслеживание просмотров экранов флоу. Когда пользователь закроет онбординг-флоу после этого экрана, вычислите ИМТ. Затем создайте представление пейвола и передайте ИМТ в качестве значения кастомного тега bmi.

Метод flowViewDidReceiveAnalyticEvent разбирает параметры события с помощью библиотеки kotlinx.serialization.json. Если в вашем проекте её ещё нет, добавьте org.jetbrains.kotlinx:kotlinx-serialization-json в зависимости приложения. Затем реализуйте следующие методы в наблюдателе, который регистрируется через AdaptyUI.setFlowsEventsObserver:


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». Чтобы использовать разделитель, принятый в локали пользователя, форматируйте ИМТ с помощью локале-зависимого форматтера на каждой платформе: например, java.text.NumberFormat на Android и NSNumberFormatter на iOS.

Сохранение ИМТ в профиль пользователя

Чтобы показывать пользователям разные флоу в зависимости от их ИМТ, сохраните его как пользовательский атрибут профиля. В дашборде Adapty создайте пользовательский атрибут типа Number с ключом bmi. Затем назначьте каждый флоу на аудиторию, сформированную на основе этого атрибута.

Вызовите метод updateProfile в flowViewDidDisappear, сразу после строки, в которой вычисляется bmi. Передавайте Double, а не форматированную строку — это позволит сегменту сравнивать ИМТ с пороговым значением:


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

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