# ANDROID - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.673Z Total files: 41 --- # File: sdk-installation-android --- --- title: "Установка и настройка Android SDK" description: "Пошаговое руководство по установке Adapty SDK на Android для приложений с подписками." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше мобильное приложение: - **Core Adapty**: основной SDK, без которого Adapty не будет работать. - **AdaptyUI**: модуль, необходимый при использовании [Adapty Paywall Builder](adapty-paywall-builder) — удобного no-code инструмента для создания кросс-платформенных пейволов. AdaptyUI активируется автоматически вместе с основным модулем. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наше [демо-приложение](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app), которое демонстрирует полную настройку: отображение пейволов, совершение покупок и другой базовый функционал. ::: ## Требования \{#requirements\} Минимальная версия SDK: `minSdkVersion 21` :::info Adapty совместим с Google Play Billing Library версий до 8.x включительно. По умолчанию Adapty работает с Google Play Billing Library v.7.0.0, но если вы хотите принудительно использовать более позднюю версию, добавьте [зависимость](https://developer.android.com/google/play/billing/integrate#dependency) вручную. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установите Adapty SDK \{#install-adapty-sdk\} Выберите способ настройки зависимостей: - Стандартный Gradle: добавьте зависимости в `build.gradle` **на уровне модуля** - Если в проекте используются файлы `.gradle.kts`, добавьте зависимости в `build.gradle.kts` на уровне модуля - Если вы используете каталоги версий, добавьте зависимости в файл `libs.versions.toml`, а затем сошлитесь на него в `build.gradle.kts` [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Android.svg?style=flat&logo=android)](https://github.com/adaptyteam/AdaptySDK-Android/releases) ```groovy showLineNumbers dependencies { ... implementation platform('io.adapty:adapty-bom:') implementation 'io.adapty:android-sdk' // Only add this line if you plan to use Paywall Builder implementation 'io.adapty:android-ui' } ``` ```kotlin showLineNumbers dependencies { ... implementation(platform("io.adapty:adapty-bom:")) implementation("io.adapty:android-sdk") // Добавьте эту строку только если планируете использовать Paywall Builder: implementation("io.adapty:android-ui") } ``` ```toml showLineNumbers //libs.versions.toml [versions] .. adaptyBom = "" [libraries] .. adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } adapty = { module = "io.adapty:android-sdk" } // Only add this line if you plan to use Paywall Builder: adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... implementation(platform(libs.adapty.bom)) implementation(libs.adapty) // Only add this line if you plan to use Paywall Builder: implementation(libs.adapty.ui) } ``` Если зависимость не разрешается, убедитесь, что в ваших Gradle-скриптах есть `mavenCentral()`.
Инструкция по добавлению Если в вашем `settings.gradle` нет `dependencyResolutionManagement`, добавьте следующее в корневой `build.gradle` в конец блока repositories: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` В противном случае добавьте следующее в `settings.gradle` в раздел `repositories` секции `dependencyResolutionManagement`: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... mavenCentral() } } ```
:::important Adapty Android SDK 4.0 находится в стадии пре-релиза. Gradle не выбирает пре-релизные версии через динамические диапазоны версий (например, `+` или `latest.release`), поэтому необходимо указать точную версию. Укажите версию `adapty-bom` для пре-релиза 4.0 — например `io.adapty:adapty-bom:4.0.0-beta.2` или `adaptyBom = "4.0.0-beta.2"` в `libs.versions.toml`. BOM автоматически подбирает совместимые версии `android-sdk` и `android-ui`. См. [Миграция на Adapty Android SDK v4](migration-to-android-sdk-v4). ::: ## Активация модуля Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} ### Базовая настройка \{#basic-setup\} Активируйте Adapty SDK в коде вашего приложения. :::note Adapty SDK нужно активировать в приложении только один раз. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. ```kotlin showLineNumbers // In your Application class class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ) } } ``` ```java showLineNumbers // In your Application class public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( getApplicationContext(), new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ); } } ``` :::important Дождитесь завершения `Adapty.activate` перед вызовом любых других методов SDK. Полная последовательность описана в разделе [Порядок вызовов в Android SDK](android-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), следуйте [быстрому старту с Paywall Builder](android-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, смотрите [быстрый старт для кастомных пейволов](android-quickstart-manual). ## Активация модуля AdaptyUI Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder), вам нужен модуль AdaptyUI. Он активируется автоматически при активации основного модуля — никаких дополнительных действий не требуется. ## Настройка Proguard \{#configure-proguard\} Перед выпуском приложения в продакшн добавьте `-keep class com.adapty.** { *; }` в вашу конфигурацию Proguard. ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы помочь вам разобраться в происходящем. Доступны следующие уровни: | Уровень | Описание | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.NONE` | Ничего не будет записано в лог. Значение по умолчанию | | `AdaptyLogLevel.ERROR` | Будут записываться только ошибки | | `AdaptyLogLevel.WARN` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания. | | `AdaptyLogLevel.INFO` | Будут записываться ошибки, предупреждения и различные информационные сообщения. | | `AdaptyLogLevel.VERBOSE` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | Вы можете задать уровень логирования в приложении до настройки Adapty. ```kotlin showLineNumbers Adapty.logLevel = AdaptyLogLevel.VERBOSE //recommended for development and the first production release ``` ```java showLineNumbers Adapty.setLogLevel(AdaptyLogLevel.VERBOSE); //recommended for development and the first production release ``` #### Перенаправление сообщений системы логирования \{#redirect-the-logging-system-messages\} Если по какой-то причине вам нужно отправлять сообщения от Adapty в вашу систему или сохранять их в файл, вы можете переопределить поведение по умолчанию: ```kotlin showLineNumbers Adapty.setLogHandler { level, message -> //handle the log } ``` ```java showLineNumbers Adapty.setLogHandler((level, message) -> { //handle the log }); ``` ### Политики работы с данными \{#data-policies\} Adapty не хранит персональные данные пользователей, если только вы не передаёте их явно. При этом вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства отдельных стран. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA) или сокращения лишнего сбора данных, если функции на основе IP-адреса не нужны вашему приложению. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build(); ``` #### Отключение сбора и передачи рекламного идентификатора (Ad ID) \{#disable-advertising-id-ad-id-collection-and-sharing\} При активации модуля Adapty установите `adIdCollectionDisabled` в значение `true`, чтобы отключить сбор [рекламного идентификатора](https://support.google.com/googleplay/android-developer/answer/6048248) пользователя. Значение по умолчанию — `false`. Используйте этот параметр для соблюдения требований Play Store, чтобы избежать показа запроса разрешения на доступ к идентификатору рекламы, или если ваше приложение не требует атрибуции рекламы или аналитики на основе Ad ID. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build(); ``` #### Настройка конфигурации кэша медиа для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевой нагрузки. Вы можете настроить параметры кэша, передав пользовательскую конфигурацию. Используйте `AdaptyUI.configureMediaCache`, чтобы переопределить размер кэша и срок его действия по умолчанию. Это необязательно — если вы не вызываете этот метод, будут использоваться значения по умолчанию (100 МБ на диске, 7 дней). ```kotlin showLineNumbers val cacheConfig = MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(3.days) .build() AdaptyUI.configureMediaCache(cacheConfig) ``` ```java showLineNumbers MediaCacheConfiguration cacheConfig = new MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(TimeInterval.days(3)) .build(); AdaptyUI.configureMediaCache(cacheConfig); ``` **Параметры:** | Параметр | Наличие | Описание | |------------------------|----------|-----------------------------------------------------------------------------| | diskStorageSizeLimit | optional | Общий размер кэша на диске в байтах. По умолчанию — 100 МБ. | | diskCacheValidityTime | optional | Как долго кэшированные файлы считаются актуальными. По умолчанию — 7 дней. | :::tip Вы можете очистить медиакэш во время выполнения с помощью `AdaptyUI.clearMediaCache(strategy)`, где `strategy` может принимать значение `CLEAR_ALL` или `CLEAR_EXPIRED_ONLY`. ::: ### Задайте обфусцированные идентификаторы аккаунта \{#set-obfuscated-account-ids\} Google Play требует обфусцированные идентификаторы аккаунта в ряде сценариев — для защиты конфиденциальности и безопасности пользователей. Эти идентификаторы помогают Google Play отслеживать покупки, не раскрывая личные данные пользователей, что особенно важно для предотвращения мошенничества и аналитики. Задавать такие идентификаторы может потребоваться, если приложение работает с чувствительными данными пользователей или если вы обязаны соблюдать определённые нормы конфиденциальности. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские идентификаторы. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build(); ``` ### Запуск Adapty в другом процессе \{#run-adapty-in-a-custom-process\} По умолчанию Adapty может работать только в главном процессе вашего приложения. Если ваше приложение использует несколько процессов, инициализируйте Adapty только один раз — иначе возможно непредсказуемое поведение. Если вам нужно запустить Adapty в другом процессе, укажите его в конфигурации: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build(); ``` Если вы попытаетесь активировать Adapty в другом процессе, не задав это значение, SDK выведет предупреждение и пропустит активацию. ### Включение локальных уровней доступа \{#enable-local-access-levels\} По умолчанию [локальные уровни доступа](local-access-levels) на Android отключены. Чтобы включить их, установите `withLocalAccessLevelAllowed` в `true`: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build(); ``` ## Устранение неполадок \{#troubleshooting\} #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются со собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, в которых определены правила резервного копирования, объединение Android-манифеста может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/sample_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` Чтобы решить эту проблему, вам нужно: - Указать merge-механизму манифеста использовать значения атрибутов резервного копирования из вашего приложения. - Объединить правила резервного копирования из Adapty и других SDK в один XML-файл (или пару файлов для Android 12+). #### 1. Добавьте пространство имён `tools` в манифест \{#1-add-the-tools-namespace-to-your-manifest\} Если оно ещё не добавлено, добавьте пространство имён `tools` в корневой тег ``: ```xml ... ``` #### 2. Переопределите атрибуты резервного копирования в `` \{#2-override-backup-attributes-in-application\} В файле `AndroidManifest.xml` вашего приложения обновите тег ``, чтобы приложение предоставляло финальные значения и сообщало инструменту слияния манифестов о необходимости заменить значения из библиотек: ```xml ... ``` Если какой-либо SDK также устанавливает `android:allowBackup`, добавьте его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования \{#3-create-merged-backup-rules-files\} Создайте XML-файлы в директории `app/src/main/res/xml/`, которые объединяют правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для любых других SDK, которые вы используете в своём приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" ``` **Для Android 11 и ниже** (использует устаревший формат содержимого полного резервного копирования): ```xml title="sample_backup_rules.xml" ``` С такой настройкой: - Исключения резервного копирования Adapty (`AdaptySDKPrefs.xml`) сохраняются. - Исключения других SDK (например, `appsflyer-data`) также применяются. - Инструмент слияния манифестов использует конфигурацию вашего приложения и больше не завершается ошибкой из-за конфликтующих атрибутов резервного копирования. #### Ошибки покупок после возврата из другого приложения \{#purchases-fail-after-returning-from-another-app\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или переиспользовать его при возврате пользователя из Google Play, банковского приложения или браузера. Это может привести к потере результата покупки или её обработке как отменённой. Чтобы покупки работали корректно, используйте только режимы `standard` или `singleTop` для Activity, запускающего флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающий флоу покупки, имеет режим `standard` или `singleTop`: ```xml ``` --- # File: android-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в Android SDK" description: "Быстрый старт по включению встроенных покупок с помощью Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно понять три ключевых понятия: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые показывают продукты пользователям; создаются в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](android-quickstart-manual). - [**Плейсменты**](placements) – где и когда показывать флоу в приложении (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Просто | Вы [создаёте готовый к покупке флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отображает его и берёт на себя весь сложный процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в настройке продуктов. См. [гайд](android-quickstart-manual). | | Режим наблюдателя | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Учтите, что режим наблюдателя имеет ограничения в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете строить UI пейвола самостоятельно, см. [Реализация пейволов вручную](android-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде вашего приложения нужно всего лишь: 1. **Получить флоу**: Запросить его из Adapty. 2. **Отобразить его — покупки Adapty обработает за вас**: Показать представление в приложении. 3. **Обработать действия кнопок**: Связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии на кнопки. ## Перед началом работы \{#before-you-start\} Перед началом выполните следующие шаги: 1. [Подключите приложение к Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-android) в коде приложения. В этом гайде используются API Adapty Android SDK v4. :::tip Самый быстрый способ выполнить эти шаги — следовать [руководству по быстрому старту](quickstart) или создать флоу и плейсменты с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получите флоу \{#1-get-the-flow\} Ваши флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, нужно: 1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow` и проверить, содержит ли он конфигурацию представления. 2. Получить конфигурацию представления с помощью метода `getFlowConfiguration`. Конфигурация представления содержит элементы интерфейса и стили, необходимые для отображения флоу. :::important Чтобы получить конфигурацию представления, необходимо включить переключатель **Show on device** во Flow Builder. В противном случае вы получите пустую конфигурацию представления, и флоу не будет отображён. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { val flow = result.value if (!flow.hasViewConfiguration) { return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); if (!flow.hasViewConfiguration()) { return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); // use loaded configuration } }); } }); ``` ## 2. Отображение флоу \{#display-the-flow\} Теперь, когда у вас есть конфигурация флоу, достаточно добавить несколько строк, чтобы отобразить его. Чтобы отобразить визуальный флоу на экране устройства, необходимо сначала его настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, null, // products = null means auto-fetch eventListener, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null означает автозагрузку eventListener ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //добавьте в иерархию представлений, если нужно, или получите из xml ... flowView.showFlow(flowConfiguration, products, eventListener); ``` ```xml showLineNumbers ``` После успешного создания view вы можете добавить его в иерархию отображения и показать на экране устройства. :::tip Подробнее о том, как отображать флоу, читайте в нашем [гайде](android-present-paywalls). ::: ## 3. Обработка действий кнопок \{#handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Android SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и открытие ссылок. Однако у других кнопок есть пользовательские или предустановленные ID, и обработку их действий нужно реализовывать в коде. Также вы можете переопределить их поведение по умолчанию. Например, вот поведение по умолчанию для кнопки закрытия. Добавлять его в код не обязательно, но ниже показано, как это делается при необходимости. :::tip Ознакомьтесь с нашими гайдами по обработке [действий](android-handle-paywall-actions) и [событий](android-handling-events) кнопок. ::: ```kotlin showLineNumbers title="Kotlin" override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ```java showLineNumbers @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](android-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно объединить в вашем приложении. ```kotlin showLineNumbers title="Kotlin" class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Adapty.getFlow("YOUR_PLACEMENT_ID") { flowResult -> if (flowResult is AdaptyResult.Success) { val flow = flowResult.value if (!flow.hasViewConfiguration) { // Use custom logic return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value val flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch object : AdaptyFlowDefaultEventListener() { override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Close -> { (context as? Activity)?.onBackPressed() } } } } ) setContentView(flowView) } } } } } } ``` ```java showLineNumbers public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Adapty.getFlow("YOUR_PLACEMENT_ID", flowResult -> { if (flowResult instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) flowResult).getValue(); if (!flow.hasViewConfiguration()) { // Use custom logic return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); AdaptyFlowView flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch new AdaptyFlowDefaultEventListener() { @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } } ); setContentView(flowView); } }); } }); } } ``` --- # File: android-check-subscription-status --- --- title: "Проверка статуса подписки в Android SDK" description: "Узнайте, как проверить статус подписки в приложении Android с помощью Adapty." --- Чтобы решить, предоставить ли пользователю доступ к платному контенту или показать пейвол, нужно проверить его [уровень доступа](access-level) в профиле. В этой статье рассказывается, как обращаться к состоянию профиля, чтобы решить, что показывать пользователю — пейвол или платные функции. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Для этого есть два варианта: - Вызовите `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или хотите принудительно обновить профиль. - Настройте **автоматические обновления профиля**, чтобы хранить локальную копию, которая обновляется автоматически при изменении статуса подписки. ### Получение профиля \{#get-profile\} Самый простой способ узнать статус подписки — вызвать метод `getProfile`: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `Adapty.setOnProfileUpdatedListener()` для отслеживания изменений профиля — Adapty будет автоматически вызывать этот метод при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при вызове этого метода, чтобы использовать их в приложении без лишних сетевых запросов. ```kotlin class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true } } ``` ```java public class SubscriptionManager { private AdaptyProfile currentProfile; public SubscriptionManager() { // Listen for profile updates Adapty.setOnProfileUpdatedListener(profile -> { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() public boolean hasAccess() { if (currentProfile == null) { return false; } AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); return premiumAccess != null && premiumAccess.isActive(); } } ``` :::note Adapty автоматически вызывает слушатель обновлений профиля при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно сразу принять решение о показе пейвола или предоставлении доступа к платным функциям, можно напрямую проверить профиль пользователя. Это удобно при запуске приложения, входе в премиум-разделы или перед показом определённого контента. ```kotlin private fun initializePaywall() { loadPaywall { paywallView -> checkAccessLevel { result -> when (result) { is AdaptyResult.Success -> { if (!result.value && paywallView != null) { setContentView(paywallView) // Show paywall if no access } } is AdaptyResult.Error -> { if (paywallView != null) { setContentView(paywallView) // Show paywall if access check fails } } } } } } private fun checkAccessLevel(callback: ResultCallback) { Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true callback.onResult(AdaptyResult.Success(hasAccess)) } is AdaptyResult.Error -> { callback.onResult(AdaptyResult.Error(result.error)) } } } } ``` ```java private void initializePaywall() { loadPaywall(paywallView -> { checkAccessLevel(result -> { if (result instanceof AdaptyResult.Success) { boolean hasAccess = ((AdaptyResult.Success) result).getValue(); if (!hasAccess && paywallView != null) { setContentView(paywallView); // Show paywall if no access } } else if (result instanceof AdaptyResult.Error) { if (paywallView != null) { setContentView(paywallView); // Show paywall if access check fails } } }); }); } private void checkAccessLevel(ResultCallback callback) { Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); boolean hasAccess = premiumAccess != null && premiumAccess.isActive(); callback.onResult(AdaptyResult.success(hasAccess)); } else if (result instanceof AdaptyResult.Error) { callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError())); } }); } ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](android-quickstart-identify), чтобы они получали доступ к тому, за что заплатили. --- # File: android-quickstart-identify --- --- title: "Идентификация пользователей в Android SDK" description: "Краткое руководство по настройке Adapty для управления встроенными покупками в Android." --- :::important Этот гайд для вас, если у вас есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей существующей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации вашего приложения: - Если ваше приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, смотрите [раздел об анонимных пользователях](#anonymous-users). - Если ваше приложение использует (или будет использовать) бэкенд-аутентификацию, смотрите [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой авторизации. Вот чем различаются анонимные и идентифицированные пользователи: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **Purchase management** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах благодаря customer user ID | | **Profile management** | Новый профиль при каждой переустановке | Один и тот же профиль в разных сессиях и на разных устройствах | | **Data persistence** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно обрабатывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю в Adapty и его аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новое устройство**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Таким образом, для анонимных пользователей при каждой установке будет создаваться новый профиль — но это не проблема, поскольку в аналитике Adapty можно [настроить, что считается новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей установки нужно считать по **идентификаторам устройств**. В этом случае каждая установка приложения на устройство считается отдельной установкой, включая повторные. ## Идентифицированные пользователи \{#identified-users\} У вас есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который уже связан с другим Customer User ID, уровень доступа становится общим — оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ переносился с одного профиля на другой, или полностью отключить совместное использование. Подробнее — в [статье](general#6-sharing-paid-access-between-user-accounts). ::: ### В момент входа или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **ещё не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей (customer user ID) должны быть уникальными для каждого пользователя. Если задать значение параметра жёстко в коде, все пользователи будут считаться одним. ::: Дождитесь срабатывания колбэка завершения `identify`, прежде чем вызывать другие методы SDK. Параллельные вызовы могут попасть в анонимный профиль вместо идентифицированного. См. [Порядок вызовов в Android SDK](android-sdk-call-order). ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Уникальный для каждого пользователя if (error == null) { // успешная идентификация } } ``` ```java showLineNumbers // ID пользователей должны быть уникальными для каждого пользователя Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // успешная идентификация } }); ``` ### Во время активации SDK \{#during-the-sdk-activation\} Если вы знаете customer user ID уже в момент активации SDK, его можно передать прямо в методе `activate` — вызывать `identify` отдельно не нужно. Если customer user ID известен, но вы задаёте его только после активации, то при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (который уже использовался ранее), так и новый. Если вы передадите новый, профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитические дашборды, поскольку установки учитываются на основе идентификаторов устройств. Идентификатор устройства соответствует одной установке приложения из стора на устройстве и обновляется только при переустановке приложения. Он не зависит от того, первая это установка или повторная, и от того, используется ли существующий customer user ID. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без переустановки не генерируют дополнительные события установки. Если вы хотите считать установки по уникальным пользователям, а не устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Идентификаторы пользователей должны быть уникальными для каждого пользователя. Если вы укажете значение параметра в коде явно, все пользователи будут считаться одним. .build(); ``` ### Выход пользователей из системы \{#log-users-out\} Если в приложении есть кнопка выхода, используйте метод `logout`. :::important При выходе из системы для пользователя создаётся новый анонимный профиль. ::: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` :::info Чтобы снова авторизовать пользователей в приложении, используйте метод `identify`. ::: ### Разрешите покупки без входа в систему \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, нужно убедиться, что после авторизации они сохранят доступ к своим покупкам: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с его идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, сохраняя всю историю покупок. - Если это существующий customer user ID (customer user ID уже привязан к профилю), нужно получить актуальный уровень доступа после переключения профиля. Можно вызвать [`getProfile`](android-check-subscription-status) сразу после идентификации или [подписаться на обновления профиля](android-check-subscription-status), чтобы данные синхронизировались автоматически. ## Следующие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении. Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](android-onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Подключите сервисы маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Установка пользовательских атрибутов профиля**](android-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-android --- --- title: "Интеграция Adapty в ваше Android-приложение с помощью навыка интеграции SDK" description: "Используйте навык adapty-sdk-integration для сквозной интеграции Adapty SDK в ваше Android-приложение с помощью AI-инструмента для написания кода." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Навык находится в бета-версии. Если он зависнет или будет работать некорректно, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor-android) — оно проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-android --- --- title: "Интеграция Adapty в Android-приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty в Android-приложение с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд проведёт вас через интеграцию Adapty в ваше Android-приложение шаг за шагом с помощью AI-инструмента для программирования — вы подаёте ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Прежде чем начать: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует некоторой настройки в дашборде до написания кода SDK. Вы можете сделать это с помощью интерактивного LLM-скилла или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настраивать приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Вам нужно лишь [подключить стор](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в вашем агенте. Он проведёт вас через каждый шаг — в том числе укажет, когда нужно открыть дашборд для подключения стора. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. LLM не может самостоятельно получить значения из дашборда — их нужно предоставить. 1. **Подключите стор**: В дашборде Adapty перейдите в **App settings → General**. Это обязательно для работы покупок. [Подключить Google Play](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигуратор Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. Вы не ссылаетесь на продукты напрямую в коде — Adapty доставляет их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: В дашборде Adapty создайте пейвол на странице **Paywalls**, затем назначьте его на плейсмент на странице **Placements**. В коде ID плейсмента — это строка, которую вы передаёте в `Adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels["premium"]?.isActive`. Уровень доступа `premium` по умолчанию подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Как только у вас есть все пять, можно писать код. Скажите своему LLM: «Мой публичный SDK-ключ — X, мой ID плейсмента — Y», чтобы он сгенерировал корректный код инициализации и получения пейвола. ::: ### Настройте, когда будете готовы \{#set-up-when-ready\} Это не нужно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: Настраиваются на странице **Placements**. Изменения в коде не нужны. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: Добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции аналитики**: Настраиваются на странице **Integrations**. Процесс настройки зависит от интеграции. См. [интеграции аналитики](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Загрузите документацию Adapty в свой LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически подтягивает нужные docs на основе вашего запроса — никакого ручного копирования ссылок. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Чтобы настроить его, выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки смотрите [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки укажите библиотеку Adapty в своих промптах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Android SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вручную вставлять ссылки на документацию, порядок реализации имеет значение. Следуйте [пошаговому руководству по реализации](#implementation-walkthrough) ниже строго по шагам, чтобы всё работало корректно. ::: ### Используйте документацию в виде обычного текста \{#use-plain-text-docs\} Любой документ Adapty доступен в виде обычного текстового Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-android.md](https://adapty.io/docs/ru/adapty-cursor-android.md). Каждый шаг в [пошаговом руководстве по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это в ваш LLM» со ссылками `.md` для вставки. Чтобы получить сразу несколько документов, см. [индексные файлы и платформенные подборки](#plain-text-doc-index-files) ниже. ## Пошаговая реализация \{#implementation-walkthrough\} Далее мы разберём интеграцию Adapty по порядку реализации. Каждый этап включает документы для передачи вашему LLM, то, что вы должны увидеть после завершения, и типичные проблемы. ### Спланируйте интеграцию \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, режим плана в Cursor или Claude Code), используйте его — так LLM сможет изучить и структуру вашего проекта, и документацию Adapty перед написанием кода. Сообщите LLM, какой подход к покупкам вы используете — от этого зависит, какими гайдами она должна руководствоваться: - [**Adapty Paywall Builder**](adapty-paywall-builder): Вы создаёте пейволы в визуальном редакторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](android-making-purchases): Вы строите собственный UI пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Режим Observer**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в быстром старте](android-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK через Gradle в Android Studio и активируйте её с помощью вашего публичного ключа SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-android) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-android.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается. В Logcat отображается лог активации Adapty. - **Возможная проблема:** «Public API key is missing» → убедитесь, что заменили placeholder на реальный ключ из **App settings**. ::: ### Показывайте пейволы и обрабатывайте покупки \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). **Гайды:** - [Включить покупки с помощью пейволов (быстрый старт)](android-quickstart-paywalls) - [Получить пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) - [Отобразить пейволы](android-present-paywalls) - [Обработать события пейвола](android-handling-events) - [Реагировать на действия кнопок](android-handle-paywall-actions) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-paywalls.md - https://adapty.io/docs/ru/android-get-pb-paywalls.md - https://adapty.io/docs/ru/android-present-paywalls.md - https://adapty.io/docs/ru/android-handling-events.md - https://adapty.io/docs/ru/android-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Пейвол отображается с настроенными продуктами. При нажатии на продукт появляется диалог покупки в песочнице. - **Возможная проблема:** Пустой пейвол или ошибка `getPaywall` → убедитесь, что ID плейсмента точно совпадает с тем, что указано в дашборде, и что плейсменту назначена аудитория. ::: **Гайды:** - [Включить покупки в своём кастомном пейволе (быстрый старт)](android-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-android) - [Отобразить пейвол, созданный через Remote Config](present-remote-config-paywalls-android) - [Совершить покупку](android-making-purchases) - [Восстановить покупки](android-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-android.md - https://adapty.io/docs/ru/present-remote-config-paywalls-android.md - https://adapty.io/docs/ru/android-making-purchases.md - https://adapty.io/docs/ru/android-restore-purchase.md :::tip[Контрольная точка] - **Ожидаемый результат:** Ваш пейвол отображает продукты, полученные из Adapty. Нажатие на продукт вызывает диалог покупки в песочнице. - **Возможная проблема:** Пустой массив продуктов → убедитесь, что к пейволу в дашборде привязаны продукты и у плейсмента есть аудитория. ::: **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-android) - [Отчёт о транзакциях в Observer mode](report-transactions-observer-mode-android) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-android.md - https://adapty.io/docs/ru/report-transactions-observer-mode-android.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая ошибка:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и настроены Google Play Real-Time Developer Notifications. ::: ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы закрыть премиум-контент. **Гайд:** [Проверка статуса подписки](android-check-subscription-status) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-check-subscription-status.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels["premium"]?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → убедитесь, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет авторизации. ::: **Гайд:** [Идентификация пользователей](android-quickstart-identify) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty.identify("your-user-id")` в разделе **Profiles** дашборда Adapty появляется ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до получения пейволов, чтобы избежать атрибуции профиля анонимному пользователю. ::: ### Подготовка к релизу \{#prepare-for-release\} Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидается:** Все пункты чеклиста подтверждены: подключение стора, серверные уведомления, флоу покупки, проверки уровня доступа и требования к конфиденциальности. - **Важно:** Если не настроены Google Play Real-Time Developer Notifications → настройте их в **App settings → Android SDK**, иначе события не будут отображаться в дашборде. ::: ## Индексные файлы документации в виде обычного текста \{#plain-text-doc-index-files\} Если вы хотите дать вашему LLM более широкий контекст, выходящий за рамки отдельных страниц, мы предоставляем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов для LLM. Обратите внимание, что для некоторых ИИ-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить его в чат как файл. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой объём — используйте только тогда, когда нужна полная картина. - Android-специфичные [`android-llms.txt`](https://adapty.io/docs/ru/android-llms.txt) и [`android-llms-full.txt`](https://adapty.io/docs/ru/android-llms-full.txt): Подборки для конкретной платформы, позволяющие сэкономить токены по сравнению с полным сайтом. --- # File: android-get-pb-paywalls --- --- title: "Получение флоу и пейволов — Android" description: "Загружайте флоу и пейволы из Adapty в своём Android-приложении." --- После того как вы [создали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, и его конфигурацию представления, как описано ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Перед тем как начать отображать флоу в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-android) в своём мобильном приложении.
## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, настроить конфигурацию представления и затем показать его в мобильном приложении. Чтобы обеспечить оптимальную производительность, важно как можно раньше получить флоу или пейвол и его [конфигурацию отображения](android-get-pb-paywalls#fetch-the-view-configuration), чтобы изображения успели загрузиться до того, как пользователь их увидит. Для получения флоу или пейвола используйте метод `getFlow`: ```kotlin showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow/paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow/paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Рекомендуем именно этот вариант — так пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильный интернет, рассмотрите вариант `.returnCacheDataElseLoad`: он возвращает кешированные данные, если они есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее при любом качестве соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит флоу и пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускорения загрузки используется CDN, а в случае его недоступности — отдельный резервный сервер. Такая архитектура обеспечивает получение актуальной версии данных и надёжность даже при слабом интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для этого метода. По истечении таймаута возвращаются кешированные данные или локальный резервный пейвол.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько запросов под капотом.

Для Android: объект `TimeInterval` можно создать с помощью функций-расширений (например, `5.seconds`, где `.seconds` из `import com.adapty.utils.seconds`) или через `TimeInterval.seconds(5)`. Чтобы не ограничивать время ожидания, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | | :-------- | :---------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, Remote Config и флаг `hasViewConfiguration`, указывающий, включает ли флоу конфигурацию отображения. Чтобы получить продукты для предзагрузки, кастомного UI или программных проверок, вызовите `getPaywallProducts(flow)`. | ## Получение конфигурации отображения \{#fetch-the-view-configuration\} После получения флоу или пейвола проверьте, содержит ли он конфигурацию отображения, с помощью `flow.hasViewConfiguration`. Этот флаг показывает, каким способом плейсмент был создан в дашборде Adapty: - **`true`** — плейсмент создан в **Flow Builder** (флоу) или **Paywall Builder** (пейвол). Adapty отрисовывает интерфейс за вас. Продолжайте выполнять шаги ниже, чтобы получить конфигурацию представления и [показать флоу или пейвол](android-present-paywalls). - **`false`** — плейсмент является пользовательским пейволом без интерфейса Builder. [Обработайте его как пейвол с Remote Config](present-remote-config-paywalls-android). :::important Убедитесь, что в Flow Builder включён переключатель **Show on device**. Если эта опция не активирована, конфигурация представления не будет доступна для получения. ::: Используйте метод `getFlowConfiguration`, чтобы загрузить конфигурацию представления. ```kotlin showLineNumbers if (!flow.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getFlowConfiguration(flow, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val flowConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Параметр | Обязательность | Описание | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **locale** |

необязательный

по умолчанию: локаль устройства

| Идентификатор [локализации](add-paywall-locale-in-adapty-paywall-builder) в виде языкового кода с одним или двумя подтегами, разделёнными дефисом (например, `en`, `pt-br`). См. [Локализации и коды локалей](android-localizations-and-locale-codes). | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. По истечении таймаута возвращаются кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, так как операция может включать несколько внутренних запросов. |
Используйте метод `getFlowConfiguration` для загрузки конфигурации представления. ```java showLineNumbers if (!flow.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getFlowConfiguration(flow, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Обязательность | Описание | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **locale** |

необязательный

по умолчанию: локаль устройства

| Идентификатор [локализации](add-paywall-locale-in-adapty-paywall-builder) в виде языкового кода с одним или двумя подтегами, разделёнными `-` (например, `en`, `pt-br`). См. [Локализации и коды языков](android-localizations-and-locale-codes). | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. Если таймаут истёк, будут возвращены кэшированные данные или локальный резервный вариант. Обратите внимание: в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может состоять из нескольких запросов под капотом. |
:::note Если вы используете несколько языков, узнайте, как добавить [локализацию в Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](android-localizations-and-locale-codes). ::: После загрузки [отобразите флоу или пейвол](android-present-paywalls). ## Получите флоу или пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают при слабом интернет-соединении, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать флоу или пейвол для аудитории по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении флоу. - **Потеря таргетинга**: все пользователи будут видеть один и тот же флоу, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или вашим собственным кастомным атрибутам). Если вы готовы принять эти недостатки ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кэшированные данные, если они есть. В таком случае пользователи могут получать не самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кэш сохраняется после перезапуска приложения и очищается только при переустановке или вручную.

| ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в своём флоу или пейволе, используйте пользовательские ассеты. Главные изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле пользовательских ассетов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разное изображение или видео отдельным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед запуском видео. Вот пример того, как передавать пользовательские ресурсы через простой словарь: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Если ресурс не найден, флоу вернётся к внешнему виду по умолчанию. ::: Для видео можно дополнительно передать `resolution`, чтобы заранее зарезервировать место в макете и задать соотношение сторон (`width / height`) до загрузки видео: ```kotlin showLineNumbers AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920), ) ```
После того как вы [разработали визуальную часть пейвола](adapty-paywall-builder) в новом Paywall Builder на дашборде Adapty, вы можете отобразить его в своём мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::warning Новый Paywall Builder работает с Android SDK версии 3.0 и выше. ::: Пожалуйста, обратите внимание, что эта тема посвящена пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к теме [Получение пейволов и продуктов для Remote Config пейволов в вашем мобильном приложении](fetch-paywalls-and-products-android). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать отображать пейволы в вашем мобильном приложении (нажмите, чтобы раскрыть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-android) в своём мобильном приложении.
## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отрисовке в коде мобильного приложения — пейвол уже содержит всю информацию о том, что и как должно отображаться. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в мобильном приложении. Для оптимальной производительности важно получать пейвол и его [конфигурацию вида](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пейвол будет показан пользователю. Для получения пейвола используйте метод `getPaywall`: ```kotlin showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` означает английский, `pt-br` — португальский (Бразилия).

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем именно этот вариант — он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad`: при наличии кеша будут возвращаться кешированные данные. В этом сценарии данные могут быть не самыми свежими, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии для сокращения количества сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.

Adapty SDK хранит пейволы локально в двух слоях: описанный выше регулярно обновляемый кеш и [резервные пейволы](fallback-paywalls). Для ускорения загрузки пейволов также используется CDN, а на случай недоступности CDN — отдельный резервный сервер. Эта система гарантирует, что вы всегда получаете актуальную версию пейволов, сохраняя надёжность даже при нестабильном интернете.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для этого метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько внутренних запросов.

Для Android: создать `TimeInterval` можно с помощью функций-расширений (например, `5.seconds`, где `.seconds` из `import com.adapty.utils.seconds`) или через `TimeInterval.seconds(5)`. Чтобы отключить ограничение, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если эта опция отключена, конфигурация отображения будет недоступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это означает, что он был создан с помощью Paywall Builder. Это подскажет вам, как отображать пейвол. Если `ViewConfiguration` присутствует, обработайте его как пейвол Paywall Builder; если нет — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls). Используйте метод `getViewConfiguration` для загрузки конфигурации отображения. ```kotlin showLineNumbers if (!paywall.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val viewConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Параметр | Наличие | Описание | | :-------------- | :------------------ | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. Если таймаут истёк, будут возвращены кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, так как операция может состоять из нескольких запросов под капотом. | Используйте метод `getViewConfiguration` для загрузки конфигурации отображения. ```java showLineNumbers if (!paywall.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.LocalizedViewConfiguration viewConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | | :----------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает таймаут для этого метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться с задержкой чуть больше указанного в `loadTimeout`, поскольку операция может включать несколько разных запросов под капотом. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию в Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](android-localizations-and-locale-codes). ::: После загрузки [покажите пейвол](android-present-paywalls). ## Получите пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают при слабом интернет-соединении, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо того, чтобы не показывать пейвол вовсе. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение информации о пейволе](#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с учётом текущей (устаревшей) версии, либо смириться с тем, что пользователи на ней могут столкнуться с проблемами неотображаемых пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает потерю персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или вашим собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения пейволов, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Android SDK 2.11.3 ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы задаёте при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается код языка, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` означает английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

| ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в пейволе, используйте кастомные ассеты. У hero-изображений и видео есть предустановленные ID: `hero_image` и `hero_video`. В пакете кастомных ассетов вы обращаетесь к этим элементам по их ID и настраиваете их поведение. Для остальных изображений и видео нужно [задать кастомный ID](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать превью перед воспроизведением видео. :::important Чтобы использовать эту функцию, обновите Adapty Android SDK до версии 3.7.0 или выше. ::: Вот пример того, как можно передавать пользовательские ресурсы через простой словарь: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Если ассет не найден, пейвол вернётся к внешнему виду по умолчанию. :::
--- # File: android-present-paywalls --- --- title: "Отображение флоу и пейволов - Android" description: "Показывайте флоу и пейволы пользователям в вашем Android-приложении." --- Если вы создали флоу или пейвол, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол содержит как то, что должно отображаться внутри него, так и то, как это должно отображаться. :::warning Этот гайд охватывает флоу и **пейволы, построенные в новом Paywall Builder** и рендеримые Adapty. Для Remote Config пейволов и [режима Observer](observer-vs-full-mode) процесс отличается. - Для отображения **пейволов Remote Config** смотрите [Отображение пейвола на основе Remote Config](present-remote-config-paywalls). - Для отображения **пейволов в режиме Observer mode** смотрите [Android — отображение пейволов Paywall Builder в режиме Observer mode](android-present-paywall-builder-paywalls-in-observer-mode) ::: Чтобы получить объект `flowConfiguration`, используемый ниже, смотрите [Получение флоу и пейволов](android-get-pb-paywalls). Чтобы отобразить визуальный флоу на экране устройства, его необходимо предварительно настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` После успешного создания вью вы можете добавить его в иерархию вью и отобразить на экране устройства. Если вы получаете `AdaptyFlowView` _не_ с помощью вызова `AdaptyUI.getFlowView()`, вам также потребуется вызвать метод `.showFlow()`. Чтобы отобразить визуальное флоу на экране устройства, его нужно предварительно настроить. Для этого используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **flowConfiguration** | обязательный | Передайте объект `AdaptyUI.FlowConfiguration` с визуальными настройками флоу. Используйте метод `AdaptyUI.getFlowConfiguration(flow)` для его загрузки. Подробнее см. в разделе [Получение конфигурации представления](android-get-pb-paywalls#fetch-the-view-configuration). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит нужные продукты. | | **eventListener** | необязательный | Передайте `AdaptyFlowEventListener` для отслеживания событий флоу. Для удобства рекомендуется использовать `AdaptyFlowDefaultEventListener`. Подробнее см. в разделе [Обработка событий флоу и пейвола](android-handling-events). | | **insets** | необязательный |

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

По умолчанию: `Unspecified` — Adapty автоматически подберёт отступы, что отлично работает для edge-to-edge флоу.

Если ваш флоу не является edge-to-edge, возможно, потребуется задать пользовательские отступы. Как это сделать — читайте в разделе [Изменение отступов флоу](android-present-paywalls#change-flow-insets) ниже.

| | **customAssets** | необязательный | Передайте объект `AdaptyCustomAssets`, чтобы заменить изображения и видео во флоу или пейволе во время выполнения. Подробнее см. в разделе [Настройка ресурсов](android-get-pb-paywalls#customize-assets). | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте флоу. Резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **timerResolver** | необязательный | Передайте резолвер сюда, если планируете использовать пользовательскую функциональность таймера. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Изменение отступов флоу \{#change-flow-insets\} Отступы — это пространство вокруг флоу, которое не даёт кликабельным элементам скрываться за системными панелями. По умолчанию Adapty автоматически подстраивает отступы, что отлично работает для полноэкранных флоу. Если ваш флоу не занимает весь экран, возможно, вам потребуются собственные отступы: - Если ни строка состояния, ни панель навигации не перекрывают `AdaptyFlowView`, используйте `AdaptyFlowInsets.None`. - Для более тонкой настройки — например, если флоу перекрывается с верхней строкой состояния, но не с нижней панелью — можно задать только `bottomInset` равным `0`, как показано в примере ниже: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view flowView.onReceiveSystemBarsInsets { insets -> val flowInsets = AdaptyFlowInsets.vertical(insets.top, 0) flowView.showFlow( flowConfiguration, products, eventListener, flowInsets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(flowView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(flowView, null); AdaptyFlowInsets flowInsets = AdaptyFlowInsets.vertical(systemBarInsets.top, 0); flowView.showFlow(flowConfiguration, products, eventListener, flowInsets); return insets; }); ``` ## Использование таймеров, заданных разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в мобильном приложении, создайте объект `timerResolver` — словарь или карту, которая сопоставляет пользовательские таймеры со строковыми значениями, подставляемыми при рендеринге флоу. Пример: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* через 1 час */ } }; ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет таймер с правильным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ## Используйте пользовательские теги \{#use-custom-tags\} Чтобы использовать пользовательские теги в мобильном приложении, создайте объект `tagResolver` — словарь или карту, которая сопоставляет пользовательские теги со строковыми значениями, заменяющими их при отрисовке флоу. Пример: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` В этом примере `USERNAME` — это пользовательский тег, который вы задали в дашборде Adapty в виде ``. `tagResolver` обеспечивает динамическую замену этого тега на указанное значение — например, `John`. Рекомендуем создавать и заполнять `tagResolver` непосредственно перед показом флоу. Когда он будет готов, передайте его в метод AdaptyUI, который вы используете для отображения флоу. ## Изменение цвета индикатора загрузки флоу \{#change-flow-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml showLineNumbers title = "XML" ```
Если вы создали пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно отображаться внутри него, так и то, как именно это должно показываться. :::warning Этот гайд предназначен **только для пейволов нового Paywall Builder**, которые требуют SDK v3.0. Процесс отображения пейволов отличается для пейволов, созданных с разными версиями Paywall Builder, пейволов на Remote Config и [режима Observer](observer-vs-full-mode). - Для отображения **пейволов на Remote Config** см. [Отрисовка пейвола на основе Remote Config](present-remote-config-paywalls). - Для отображения **пейволов в режиме Observer** см. [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode) ::: Чтобы получить объект `viewConfiguration`, используемый ниже, смотрите раздел [Получение пейволов Paywall Builder и их конфигурации](android-get-pb-paywalls). Чтобы отобразить визуальный пейвол на экране устройства, сначала необходимо его сконфигурировать. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить на экране устройства. Если вы получаете `AdaptyPaywallView` _не_ через вызов `AdaptyUI.getPaywallView()`, вам также потребуется вызвать метод `.showPaywall()`. Чтобы отобразить визуальный пейвол на экране устройства, сначала необходимо его настроить. Для этого используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfiguration** | обязательный | Передайте объект `AdaptyUI.LocalizedViewConfiguration`, содержащий визуальные данные пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **eventListener** | необязательный | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется расширить `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **insets** | необязательный |

Отступы вокруг пейвола, которые предотвращают перекрытие интерактивных элементов системными панелями.

По умолчанию: `UNSPECIFIED` — Adapty автоматически подбирает отступы, что отлично работает для пейволов на весь экран.

Если ваш пейвол не занимает весь экран, возможно, вам потребуется задать пользовательские отступы. Подробнее читайте в разделе [Изменение отступов пейвола](android-present-paywalls#change-paywall-insets) ниже.

| | **personalizedOfferResolver** | необязательный | Чтобы указать персонализированную цену ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая возвращает `true` для `AdaptyPaywallProduct` с персонализированной ценой и `false` в противном случае. | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **timerResolver** | необязательный | Передайте резолвер, если планируете использовать пользовательский таймер. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Изменение отступов пейвола \{#change-paywall-insets\} Отступы — это пространство вокруг пейвола, которое не даёт интерактивным элементам скрыться за системными панелями. По умолчанию Adapty автоматически подбирает отступы, что отлично работает для пейволов во весь экран. Если ваш пейвол не занимает весь экран, можно задать отступы вручную: - Если ни строка состояния, ни панель навигации не перекрываются с `AdaptyPaywallView`, используйте `AdaptyPaywallInsets.NONE`. - Для более нестандартных случаев, например если пейвол перекрывается с верхней строкой состояния, но не с нижней панелью, можно установить только `bottomInset` в `0`, как показано в примере ниже: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0) paywallView.showPaywall( viewConfiguration, products, eventListener, paywallInsets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, 0); paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver); return insets; }); ``` ## Использование таймеров, заданных разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в своём мобильном приложении, создайте объект `timerResolver` — словарь или карту, в которых каждый пользовательский таймер сопоставлен со строковым значением, на которое он будет заменён при отображении пейвола. Пример: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */ } }; ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет таймер с нужным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ## Использование пользовательских тегов \{#use-custom-tags\} Чтобы использовать пользовательские теги в мобильном приложении, создайте объект `tagResolver` — словарь или карту, которая сопоставляет пользовательские теги со строковыми значениями, подставляемыми при отображении пейвола. Пример: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` В этом примере `USERNAME` — это кастомный тег, который вы добавили в дашборде Adapty как ``. `tagResolver` обеспечивает динамическую замену этого тега на указанное значение — например, `John`. Рекомендуем создавать и заполнять `tagResolver` непосредственно перед показом пейвола. Когда он будет готов, передайте его в метод AdaptyUI, который вы используете для отображения пейвола. ## Изменение цвета индикатора загрузки пейвола \{#change-paywall-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml showLineNumbers title = "XML" ```
--- # File: android-handle-paywall-actions --- --- title: "Обработка действий флоу - Android" description: "Обрабатывайте действия кнопок из флоу и пейволов в Android-приложении." --- Если вы создаёте флоу или пейволы с помощью Adapty Flow Builder или Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в билдере](paywall-buttons) и назначьте ей существующее действие или создайте собственный идентификатор действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и существующие действия в коде. :::warning **Только покупки, восстановление, закрытие флоу/пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют реализации обработчика в коде приложения. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку для закрытия флоу или пейвола: 1. В билдере добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`. :::info В Android SDK действие `close` по умолчанию закрывает флоу или пейвол. При необходимости это поведение можно переопределить в коде. Например, закрытие одного флоу может запускать открытие другого. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если нужно добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в билдере и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из вашего флоу или пейвола (например, **Terms of use** или **Privacy policy**): 1. В билдере добавьте кнопку, назначьте ей действие **Open URL** и укажите нужный URL. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Android SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и укажите ID. 2. В коде приложения реализуйте обработчик для созданного вами ID действия. Например, если у вас есть другой набор предложений по подпискам или разовых покупок, можно добавить кнопку, которая откроет другой флоу или пейвол: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another flow or paywall } } } } ``` Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей уже существующее действие или создайте собственный ID действия. 2. Напишите в приложении код для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Покупки, восстановления, закрытие пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют явной реализации обработки в коде приложения. ::: ## Закрытие пейвола \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает пейвол. :::info В Android SDK действие `close` по умолчанию закрывает пейвол. Однако при необходимости вы можете переопределить это поведение в своём коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Открытие URL из пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупки), добавьте элемент **Link** в Paywall Builder и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Android SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в ваше приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Login -> { val intent = Intent(context, LoginActivity::class.java) context.startActivity(intent) } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и укажите идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений по подпискам или разовых покупок, можно добавить кнопку, которая откроет другой пейвол: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another paywall } } } } ``` --- # File: android-handling-events --- --- title: "Обработка событий флоу и пейвола — Android" description: "Обрабатывайте события флоу и пейвола в Android-приложении." --- :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и рендеринга флоу. Вам также необходимо реализовать обработку кнопок (закрытие флоу, открытие ссылок и т. д.). Подробнее — в нашем [гайде по обработке действий кнопок](android-handle-paywall-actions). ::: Флоу и пейволы, настроенные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-ссылки, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Ниже описано, как обрабатывать эти события. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: Если вам нужно контролировать или отслеживать процессы на экране покупки, реализуйте методы `AdaptyFlowEventListener`. Если вы хотите сохранить поведение по умолчанию в некоторых случаях, вы можете расширить `AdaptyFlowDefaultEventListener` и переопределить только те методы, которые нужно изменить. Ниже приведены значения по умолчанию из `AdaptyFlowDefaultEventListener`. ### События, генерируемые пользователем \{#user-generated-events\} #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Начало покупки \{#started-purchase\} Когда пользователь инициирует процесс покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Успешная, отменённая или отложенная покупка \{#successful-canceled-or-pending-purchase\} Если покупка прошла успешно, будет вызван следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Примеры событий (нажмите, чтобы развернуть) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
В этом случае рекомендуем закрыть экран. Метод не вызывается в Observer mode. Подробнее см. в разделе [Android — Отображение пейволов Paywall Builder в Observer mode](android-present-paywall-builder-paywalls-in-observer-mode). #### Неудачная покупка \{#failed-purchase\} Если покупка завершается с ошибкой, вызывается этот метод. Это включает ошибки Google Play Billing (ограничения платежей, некорректные продукты, сбои сети), ошибки верификации транзакции и системные ошибки. Обратите внимание: отмена покупки пользователем вызывает `onPurchaseFinished` с результатом отмены, а ожидающие платежи этот метод не вызывают. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Завершение навигации веб-оплаты \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Параметры:** | Параметр | Описание | |:------------|:------------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация в веб-пейволе завершилась с ошибкой; `null`, если навигация успешна. |
Примеры событий (нажмите, чтобы раскрыть) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Как его проверить — читайте в разделе [Статус подписки](android-listen-subscription-changes). #### Неудачное восстановление \{#failed-restore\} Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Обновление подписки \{#upgrade-subscription\} Когда пользователь пытается приобрести новую подписку, пока активна другая, вы можете управлять тем, как должна обрабатываться новая покупка, переопределив этот метод. У вас есть два варианта: 1. **Замените текущую подписку** на новую: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Сохранить обе подписки** (добавить новую отдельно): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Если вы не переопределяете этот метод, поведение по умолчанию — сохранить обе подписки активными (эквивалентно использованию `AdaptyPurchaseParameters.Empty`). ::: Вы также можете задать дополнительные параметры покупки при необходимости: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится неудачно, AdaptyUI сообщит об ошибке, вызвав следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Если вы вернёте `true`, AdaptyUI повторит запрос через 2 секунды. #### Ошибки рендеринга \{#rendering-errors\} Если во время рендеринга интерфейса возникает ошибка, о ней сообщается через вызов этого метода: ```kotlin showLineNumbers title="Kotlin" public override fun onError( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ```
В нормальной ситуации такие ошибки не должны возникать, поэтому, если вы с ними столкнулись, пожалуйста, сообщите нам. ### Навигация \{#navigation\} #### Системная кнопка «Назад» \{#system-back-button\} По умолчанию флоу нельзя закрыть системной кнопкой «Назад» или жестом — пользователь выходит из него только через путь, который вы задаёте: кнопку **Close** или действие `on_device_back` в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, переопределите `onBackPressed` и верните `false`, чтобы хост-активити или фрагмент обработал нажатие: ```kotlin showLineNumbers title="Kotlin" public override fun onBackPressed(context: Context): Boolean { return false // let the host handle the back press (e.g. finish the activity or pop the fragment) } ``` Этот коллбэк вызывается только тогда, когда для текущего экрана не настроено действие `on_device_back` — настроенное действие имеет приоритет и обрабатывается внутри SDK. Верните `true`, чтобы перехватить нажатие (поведение по умолчанию), или `false`, чтобы передать его хосту для самостоятельной обработки. ### Зарезервированные события \{#reserved-events\} `AdaptyFlowEventListener` объявляет несколько колбэков для функциональности, которую флоу пока не используют. Реализовывать их не нужно — `AdaptyFlowDefaultEventListener` уже предоставляет пустые реализации по умолчанию. | Метод | Описание | |:-------|:------------| | **onAnalyticEvent** | Зарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют эти события в ваш код, поэтому реализовывать его не нужно. | | **onShowAppRate** | Зарезервирован для запросов на оценку приложения из флоу. Флоу пока не инициируют запросы на оценку приложения, поэтому реализовывать его не нужно. | | **onShowRequestPermission** | Зарезервирован для запросов системных разрешений (например, push-уведомлений или доступа к камере) из флоу. Флоу пока не инициируют запросы на разрешения, поэтому реализовывать его не нужно. |
:::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отображения пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробности смотрите в нашем [гайде по обработке действий кнопок](android-handle-paywall-actions). ::: Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди них — нажатия кнопок (закрытия, URL, выбора продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Узнайте ниже, как реагировать на эти события. :::warning Это руководство предназначено **только для пейволов на новом Paywall Builder**, которые требуют Adapty SDK v3.0 или выше. ::: :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: Если вам нужно управлять процессами на экране покупки или отслеживать их, реализуйте методы `AdaptyUiEventListener`. Если вы хотите сохранить поведение по умолчанию для части случаев, можно унаследоваться от `AdaptyUiDefaultEventListener` и переопределить только те методы, которые нужно изменить. Ниже приведены значения по умолчанию из `AdaptyUiDefaultEventListener`. ### Пользовательские события \{#user-generated-events\} #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не будет вызван в режиме Observer. Подробнее см. в статье [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Успешная, отменённая или отложенная покупка \{#successful-canceled-or-pending-purchase\} Если покупка прошла успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Примеры событий (нажмите, чтобы раскрыть) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
В этом случае рекомендуем закрыть экран. Метод не вызывается в Observer mode. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в Observer mode](android-present-paywall-builder-paywalls-in-observer-mode). #### Неудачная покупка \{#failed-purchase\} Если покупка завершается ошибкой, этот метод будет вызван. Это включает ошибки Google Play Billing (ограничения платежей, недействительные продукты, сбои сети), ошибки проверки транзакций и системные ошибки. Обратите внимание, что отмена покупки пользователем вызывает `onPurchaseFinished` с результатом отмены, а ожидающие платежи не вызывают этот метод. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Завершение навигации веб-платежа \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Параметры:** | Параметр | Описание | |:------------|:-----------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если при навигации на веб-пейвол произошла ошибка; `null`, если навигация успешна. |
Примеры событий (нажмите, чтобы развернуть) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Мы рекомендуем закрывать экран, если у пользователя есть необходимый `accessLevel`. Подробнее о том, как это проверить, читайте в разделе [Статус подписки](android-listen-subscription-changes). #### Ошибка восстановления \{#failed-restore\} Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Обновление подписки \{#upgrade-subscription\} Когда пользователь пытается купить новую подписку, пока активна другая, вы можете управлять тем, как следует обработать новую покупку, переопределив этот метод. Доступны два варианта: 1. **Замените текущую подписку** на новую: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Оставить обе подписки** (добавить новую отдельно): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Если вы не переопределяете этот метод, по умолчанию обе подписки остаются активными (эквивалентно использованию `AdaptyPurchaseParameters.Empty`). ::: При необходимости можно задать дополнительные параметры покупки: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ``` Если новая подписка приобретается, пока другая ещё активна, переопределите этот метод, чтобы заменить текущую подписку на новую. Если активная подписка должна оставаться активной, а новая добавляется отдельно, вызовите `onSubscriptionUpdateParamsReceived(null)`: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingSubscriptionUpdateParams( product: AdaptyPaywallProduct, context: Context, onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, ) { onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) } ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получает необходимые объекты с сервера. Если эта операция завершается с ошибкой, AdaptyUI сообщает о ней, вызывая следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Если вы вернёте `true`, AdaptyUI повторит запрос через 2 секунды. #### Ошибки рендеринга \{#rendering-errors\} Если в процессе рендеринга интерфейса возникнет ошибка, она будет передана через этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRenderingError( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
В нормальных условиях такие ошибки не должны возникать, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам.
--- # File: android-use-fallback-paywalls --- --- title: "Android - Использование резервных пейволов" description: "Обрабатывайте случаи, когда пользователи оффлайн или серверы Adapty недоступны." --- :::warning Резервные пейволы поддерживаются Android SDK версии 2.11 и выше. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} 1. Переместите файл конфигурации резервного пейвола в директорию `assets` или `res/raw` вашего Android-проекта. 2. Вызовите метод `.setFallback` **до** того, как будете получать нужный флоу, пейвол или онбординг. ```kotlin showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory val location = FileLocation.fromAsset("android_fallback.json") //or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory val location = FileLocation.fromResId(context, R.raw.android_fallback) //you can also pass a file URI val fileUri: Uri = //get Uri for the file with fallback paywalls val location = FileLocation.fromFileUri(fileUri) //pass the file location Adapty.setFallback(location, callback) ``` ```java showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory FileLocation location = FileLocation.fromAsset("android_fallback.json"); //or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback); //you can also pass a file URI Uri fileUri = //get Uri for the file with fallback paywalls FileLocation location = FileLocation.fromFileUri(fileUri); //pass the file location Adapty.setFallback(location, callback); ``` | Параметр | Описание | | :----------- | :----------------------------------------------------------- | | **location** | Объект [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) для файла резервной конфигурации | --- # File: android-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали в Android SDK" description: "Управление локализациями и кодами локали для охвата глобальной аудитории (Android)." --- ## Почему это важно \{#why-this-is-important\} Коды локали используются в нескольких сценариях — например, когда нужно получить правильный пейвол для текущей локализации приложения. Поскольку коды локали устроены непросто и могут различаться в зависимости от платформы, мы используем внутренний стандарт для всех поддерживаемых платформ. Тем не менее именно из-за этой сложности важно понимать, что именно вы отправляете на наш сервер и что происходит дальше — чтобы всегда получать ожидаемый результат. ## Стандарт кодов локали в Adapty \{#locale-code-standard-at-adapty\} Для кодов локали Adapty использует слегка модифицированный стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов локали \{#locale-code-matching\} Когда Adapty получает запрос от клиентского SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Затем выполняется поиск локализации с полностью совпадающим кодом локали 3. Если совпадений не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск по ней 4. Если совпадений снова не найдено, возвращается локализация по умолчанию — `en` Таким образом устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Рекомендуемый способ реализации локализаций \{#implementing-localizations-recommended-way\} Если вас интересуют локализации, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить в каждый из этих файлов пару ключ-значение с нужным кодом локали Adapty для соответствующей локализации, а затем извлекать значение по этому ключу при вызове нашего SDK: ```kotlin showLineNumbers // 1. Modify your strings.xml files /* strings.xml - Spanish */ es /* strings.xml - Portuguese (Brazil) */ pt-br // 2. Extract and use the locale code val localeCode = context.getString(R.string.adapty_paywalls_locale) // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Это позволяет полностью контролировать, какая локализация будет загружена для каждого пользователя вашего приложения. ## Альтернативный способ реализации локализаций \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться без явного определения кодов локали для каждой локализации. Для этого можно извлечь код локали из других объектов, предоставляемых платформой: ```kotlin showLineNumbers val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.resources.configuration.locales[0] else context.resources.configuration.locale val localeCode = locale.toLanguageTag() // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Обратите внимание: этот подход мы не рекомендуем, поскольку сложно предсказать, что именно получит сервер Adapty. Если вы всё же решите использовать этот подход — убедитесь, что учли все актуальные сценарии использования. --- # File: android-web-paywall --- --- title: "Реализация веб-пейволов в Android SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок Play Store." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.15 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы работаете с пейволом, разработанным самостоятельно, вам нужно обрабатывать веб-пейволы с помощью метода SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный пейвол, показанный определённому пользователю, с веб-страницей, на которую он будет перенаправлен. 2. Отслеживает момент возврата пользователей в приложение и затем запрашивает `.getProfile` с короткими интервалами, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении практически сразу. :::note После того как пользователи вернутся в приложение, обновите UI, чтобы отразить изменения профиля. Adapty получит и обработает события обновления профиля. ::: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error } } ``` :::note Существует две версии метода `openWebPaywall`: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет в URL данные о продукте. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных о продукте. Используйте его, когда продукты в пейволе Adapty отличаются от тех, что указаны в веб-пейволе. ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить более плавный пользовательский опыт, можно открывать веб-пейволы во встроенном браузере. Это отображает страницу веб-покупки прямо внутри приложения, позволяя пользователям завершать транзакции без переключения между приложениями. Для этого установите параметр `presentation` в значение `AdaptyWebPresentation.InAppBrowser`: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, presentation = AdaptyWebPresentation.InAppBrowser, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error val adaptyError = error } } ``` --- # File: android-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder в Android SDK" description: "Устранение неполадок Paywall Builder в Android SDK" --- Этот гайд поможет вам устранить типичные проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в Android SDK. ## Получение конфигурации пейвола завершается ошибкой \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `getViewConfiguration` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. ## Число просмотров пейвола слишком велико \{#the-paywall-view-number-is-too-big\} **Проблема**: счётчик просмотров пейвола показывает вдвое больше ожидаемого значения. **Причина**: возможно, вы вызываете `logShowFlow` (Android SDK v4+) / `logShowPaywall` в своём коде, что дублирует счётчик просмотров, если вы используете Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать данный метод не нужно. **Решение**: убедитесь, что вы не вызываете `logShowFlow` (Android SDK v4+) / `logShowPaywall` в своём коде, если используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: У вас возникают другие проблемы, связанные с Paywall Builder, не описанные выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](android-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: android-quickstart-manual --- --- title: "Включение покупок в кастомном пейволе в Android SDK" description: "Интегрируйте Adapty SDK в свои кастомные Android пейволы для поддержки встроенных покупок." --- В этом гайде описано, как интегрировать Adapty в кастомные пейволы. Вы сохраняете полный контроль над реализацией пейвола, а Adapty SDK берёт на себя получение продуктов, обработку новых покупок и восстановление предыдущих. :::important **Этот гайд предназначен для разработчиков, которые реализуют кастомные пейволы.** Если вы хотите подключить покупки самым простым способом, используйте [Adapty Flow Builder](android-quickstart-paywalls). С Flow Builder вы создаёте флоу в визуальном редакторе без кода, Adapty берёт на себя всю логику покупок, а тестировать разные дизайны можно без повторной публикации приложения. ::: ## Перед началом работы \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы включить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет менять продукты, цены и офферы без обновления кода приложения. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по идентификатору плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, вам нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит вам получать ваши продукты. Чтобы разобраться, что нужно сделать в дашборде, следуйте [quickstart-гайду](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с серверной аутентификацией, так и без неё. Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по идентификации](android-quickstart-identify), чтобы разобраться в деталях и корректно работать с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, необходимо: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value Adapty.getPaywallProducts(flow) { productResult -> when (productResult) { is AdaptyResult.Success -> { val products = productResult.value // Use products to build your custom paywall UI } is AdaptyResult.Error -> { val error = productResult.error // Handle the error } } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); Adapty.getPaywallProducts(flow, productResult -> { if (productResult instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) productResult).getValue(); // Use products to build your custom paywall UI } else if (productResult instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) productResult).getError(); // Handle the error } }); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Шаг 2. Обработка покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Он обработает процесс покупки и вернёт обновлённый профиль. ```kotlin showLineNumbers fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) { Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) { Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // Покупка успешна, профиль обновлён } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Пользователь отменил покупку } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Покупка ожидает обработки (например, пользователь оплатит наличными офлайн) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Обработать ошибку } }); } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Google Play и другие сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Restore successful, profile updated } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void restorePurchases() { Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // Restore successful, profile updated } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка с пейвола проходит успешно. Чтобы увидеть, как это работает в готовом к продакшену приложении, изучите [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt) в нашем примере приложения — там показана обработка покупок с полноценной обработкой ошибок, обратной связью с интерфейсом и управлением подписками. Затем [проверьте, завершил ли пользователь покупку](android-check-subscription-status), чтобы определить, показывать ли пейвол или предоставлять доступ к платным функциям. --- # File: fetch-paywalls-and-products-android --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Android SDK" description: "Получайте пейволы и продукты в Android SDK Adapty для улучшения монетизации пользователей." --- Прежде чем отображать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Если вас интересует получение флоу или пейволов, настроенных в **Flow Builder** или **Paywall Builder**, обратитесь к разделу [Получение флоу и пейволов](android-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-android) в мобильном приложении.
## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) представляет собой комбинацию продуктов из App Store и Google Play. Эти кросс-платформенные продукты интегрируются во флоу и пейволы, позволяя отображать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, необходимо получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не захардкодивайте идентификаторы продуктов.** Единственный ID, который можно захардкодить — это ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных предложений может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если флоу сегодня возвращает два продукта, а завтра три, отображайте все без изменения кода. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. || **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае неудачи. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad`: он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](android-use-fallback-paywalls). Также используется CDN для более быстрой загрузки флоу и пейволов, а также отдельный резервный сервер на случай недоступности CDN.

| | **loadTimeout** | по умолчанию: 5 сек |

Это значение ограничивает тайм-аут метода. При его истечении будут возвращены кешированные данные или локальный фолбэк.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько запросов.

| Не задавайте product ID жёстко в коде! Так как флоу настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать эти 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно задать жёстко, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, массив `remoteConfigs` (по одной записи на каждую настроенную локаль) и флаг `hasViewConfiguration`. Чтобы получить продукты для флоу, вызовите `getPaywallProducts(flow)`. | :::note В версии v4 параметр `locale` переехал из `getFlow` в `getFlowConfiguration` (используется только при отображении через AdaptyUI). Для кастомных пейволов все доступные локали возвращаются вместе в `flow.remoteConfigs` — выберите ту, которая соответствует языку устройства пользователя или настройке вашего приложения. ::: ## Получение продуктов \{#fetch-products\} Получив флоу, вы можете запросить массив продуктов, соответствующих ему: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(flow, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // запрошенные продукты } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработка ошибки } }); ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна флоу вам, скорее всего, потребуется доступ к этим свойствам объекта [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание, что локализация основана на стране стора, выбранной пользователем, а не на локали самого устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену как числовое значение через `product.price.amount` — оно будет указано в локальной валюте. Чтобы получить соответствующий символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период подписки (например, неделя, месяц, год и т. д.), используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionDetails?.subscriptionPeriod`. Из этого объекта можно обратиться к перечислению `unit`, чтобы получить единицу длительности (`DAY`, `WEEK`, `MONTH`, `YEAR` или `UNKNOWN`). Значение `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. В каждом объекте фазы доступны следующие полезные свойства:
• `paymentMode`: перечисление со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатные пробные периоды имеют тип `FREE_TRIAL`.
• `price`: сниженная цена в числовом формате. Для бесплатных пробных периодов здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства, описывающая продолжительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативный способ получить отдельные детали периода предложения. Работает так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку флоу с помощью флоу для аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают с медленным интернетом, загрузка флоу может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу по умолчанию, чтобы обеспечить комфортный пользовательский опыт вместо пустого экрана. Чтобы решить эту задачу, можно воспользоваться методом `getFlowForDefaultAudience`, который загружает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать флоу с помощью метода `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-android#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных ограничений: - **Потенциальные проблемы с обратной совместимостью**: Если нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении флоу. - **Потеря таргетинга**: Все пользователи будут видеть один и тот же флоу, рассчитанный на аудиторию **All Users**, — то есть вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти недостатки ради более быстрого получения флоу, используйте метод `getFlowForDefaultAudience`, как описано ниже. В противном случае используйте `getFlow`, описанный [выше](fetch-paywalls-and-products-android#fetch-flow-information). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // запрошенный флоу } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработайте ошибку } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. || **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее вне зависимости от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или вручную.

|
Прежде чем работать с Remote Config и кастомными пейволами, нужно загрузить информацию о них. Обратите внимание, что этот раздел относится к Remote Config и кастомным пейволам. Для получения информации о загрузке пейволов, созданных с помощью Paywall Builder, обратитесь к статье [Загрузка пейволов Paywall Builder и их конфигурации](android-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать загрузку пейволов и продуктов в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-android) в своём мобильном приложении.
## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) — это сочетание продуктов из App Store и Google Play. Такие кросс-платформенные продукты интегрируются в пейволы, позволяя демонстрировать их в нужных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не прописывайте ID продуктов в коде.** Единственный ID, который стоит хардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, все они должны отображаться без изменений в коде. ::: ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается в виде языкового кода, состоящего из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` — английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](android-localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.

Однако если ваши пользователи часто сталкиваются с нестабильным интернетом, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](android-use-fallback-paywalls). Для ускорения загрузки пейволов также используется CDN, а в случае его недоступности — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает тайм-аут выполнения метода. По истечении тайм-аута будут возвращены кешированные данные или локальный резервный пейвол.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.

| Не прописывайте идентификаторы продуктов в коде! Поскольку пейволы настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 — без каких-либо изменений в коде. Единственное, что нужно прописать в коде, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) содержит: список идентификаторов продуктов, идентификатор пейвола, Remote Config и ряд других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить список продуктов, связанных с ним: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(paywall, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) с идентификатором продукта, его названием, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). Ниже приведены наиболее часто используемые свойства, однако полный список доступных свойств можно найти в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Для отображения названия продукта используйте `product.localizedTitle`. Обратите внимание: локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Для отображения локализованной цены используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену в виде числа через `product.price.amount` — значение будет указано в местной валюте. Для получения символа валюты используйте `product.price.currencySymbol`. | | **Subscription Period** | Для отображения периода (например, неделя, месяц, год и т. д.) используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionDetails?.subscriptionPeriod`. Через это свойство можно обратиться к enum `unit`, чтобы узнать единицу времени (DAY, WEEK, MONTH, YEAR или UNKNOWN). Значение `numberOfUnits` содержит количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer в подписке, используйте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:
• `paymentMode`: enum со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатный пробный период соответствует типу `FREE_TRIAL`.
• `price`: цена со скидкой в виде числа. Для бесплатного пробного периода здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая продолжительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативно, можно получить отдельные детали периода предложения с помощью этого свойства. Оно работает для предложений так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: отформатированный период подписки скидки в локали пользователя. | ## Ускорение загрузки пейвола с помощью пейвола аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают с медленным интернетом, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях стоит показывать пейвол по умолчанию — это обеспечит комфортный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-android#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на этой версии могут столкнуться с проблемами отображения пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, созданный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти ограничения ради ускоренного получения пейвола, используйте метод `getPaywallForDefaultAudience`, как описано ниже. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-android#fetch-paywall-information). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Android SDK версии 2.11.3. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` — английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию — в разделе [Локализации и коды локалей](android-localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad` для возврата кешированных данных при их наличии. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

|
--- # File: present-remote-config-paywalls-android --- --- title: "Отображение пейвола на основе Remote Config в Android SDK" description: "Узнайте, как показывать пейволы Remote Config в Adapty Android SDK для персонализации пользовательского опыта." --- Если вы настроили пейвол с помощью Remote Config, для его отображения пользователям потребуется реализовать рендеринг в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы сами контролируете, что включить и как будет выглядеть пейвол. Adapty предоставляет метод для получения Remote Config, что даёт вам полную свободу в отображении кастомного пейвола. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит одну запись `AdaptyRemoteConfig` на каждую настроенную локаль в массиве `remoteConfigs`. Выберите локаль, соответствующую предпочтениям пользователя, и считайте нужные значения. ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); AdaptyRemoteConfig config = null; for (AdaptyRemoteConfig remoteConfig : flow.getRemoteConfigs()) { if ("en".equals(remoteConfig.getLocale())) { config = remoteConfig; break; } } if (config == null && !flow.getRemoteConfigs().isEmpty()) { config = flow.getRemoteConfigs().get(0); } if (config != null && config.getDataMap().get("header_text") instanceof String) { String headerText = (String) config.getDataMap().get("header_text"); } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` На этом этапе, получив все необходимые значения, можно переходить к отрисовке и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптируется под различные размеры экранов и ориентации мобильных телефонов, обеспечивая удобный и понятный интерфейс на любых устройствах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-android#track-paywall-view-events), как описано ниже, — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, можно переходить к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](android-making-purchases). Рекомендуем [создать резервный пейвол](android-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кеша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших флоу и пейволов. Данные о покупках мы собираем автоматически, однако события просмотра нужно логировать самостоятельно — только вы знаете, когда пользователь видит флоу. Чтобы залогировать событие просмотра, вызовите `.logShowFlow(flow)` — это отразится в ваших метриках в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow)` не нужно, если вы отображаете флоу или пейволы, созданные в [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:------------------------------------------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config — вы полностью контролируете, как отобразить свой пейвол. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig(); if (remoteConfig != null) { if (remoteConfig.getDataMap().get("header_text") instanceof String) { String headerText = (String) remoteConfig.getDataMap().get("header_text"); } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработайте ошибку } }); ``` На этом этапе, получив все необходимые значения, можно приступить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под различные экраны и ориентации мобильных телефонов, обеспечивая удобный и единообразный пользовательский опыт на разных устройствах. :::warning Обязательно [записывайте событие просмотра пейвола](present-remote-config-paywalls-android#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После отображения пейвола настройте флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](android-making-purchases). Рекомендуем [создать резервный пейвол](android-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернета или кеша, обеспечивая бесперебойную работу приложения в таких ситуациях. ## Отслеживайте события просмотра пейвола \{#track-paywall-view-events\} Adapty помогает вам измерять эффективность ваших пейволов. Данные о покупках мы собираем автоматически, однако логирование просмотров пейволов требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, просто вызовите `.logShowPaywall(paywall)` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызов `.logShowPaywall(paywall)` не нужен, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:------------------------------------------------------------------------------------------------------------| | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-making-purchases --- --- title: "Совершение покупок в мобильном приложении в Android SDK" description: "Руководство по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — это ключевой шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако простого отображения пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их кастомизации. Если вы не используете Paywall Builder, для совершения покупки и разблокировки нужного контента необходимо вызывать отдельный метод `.makePurchase()`. Этот метод служит точкой входа, через которую пользователи взаимодействуют с пейволами и совершают нужные им транзакции. Если на вашем пейволе есть активный promotional offer для продукта, который пользователь собирается купить, Adapty автоматически применит его в момент покупки. :::warning Имейте в виду, что introductory offer будет применён автоматически только если вы используете пейволы, настроенные с помощью Paywall Builder. В остальных случаях вам нужно [проверить право пользователя на introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе. Кроме того, пользователям, имеющим право на introductory offer, может быть выставлена полная цена. ::: Убедитесь, что вы [выполнили начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершить покупку \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужна пошаговая инструкция?** Смотрите [гайд по быстрому старту](android-implement-paywalls-manually) — там полная реализация с контекстом. ::: ```kotlin showLineNumbers Adapty.makePurchase(activity, product, null) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` ```java showLineNumbers Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // Grant access to the paid features } } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Handle the case where the user canceled the purchase } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Handle deferred purchases (e.g., the user will pay offline with cash) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | обязательный | Объект [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

В случае успешного запроса ответ содержит этот объект. Объект [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) предоставляет исчерпывающую информацию об уровнях доступа, подписках и разовых покупках пользователя в приложении.

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

| :::warning **Примечание:** если вы используете Apple StoreKit версии ниже 2.0 и Adapty SDK версии ниже 2.9.0, вам необходимо указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) вместо этого. Данный метод в настоящее время устарел и не рекомендуется Apple. ::: ## Смена подписки при покупке \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора. В Google Play подписка не обновляется автоматически — вам нужно обработать переключение в коде мобильного приложения, как описано ниже. Чтобы заменить подписку другой на Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```kotlin showLineNumbers Adapty.makePurchase( activity, product, AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build() ) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | required | объект [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | ```java showLineNumbers Adapty.makePurchase( activity, product, new AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build(), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // successful cross-grade } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // user canceled the purchase flow } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // the purchase has not been finished yet, e.g. user will pay offline by cash } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | обязательный | объект [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | Подробнее о подписках и режимах замены можно прочитать в документации для разработчиков Google: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для повышения уровня подписки. Понижение уровня не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: реальное изменение подписки произойдёт только по окончании текущего расчётного периода. ### Управление предоплаченными планами \{#manage-prepaid-plans\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, купить невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для предоплаченных планов. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build(); ``` --- # File: android-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с Android SDK" description: "Узнайте, как восстановить покупки в Adapty, чтобы обеспечить бесперебойный пользовательский опыт." --- Восстановление покупок — это функция, которая позволяет пользователям снова получить доступ к ранее приобретённому контенту (подпискам или встроенным покупкам) без повторного списания средств. Особенно она полезна тем, кто удалил и переустановил приложение или перешёл на новое устройство и хочет получить доступ к уже оплаченному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — никакого дополнительного кода не требуется. Если вы используете именно его, этот шаг можно пропустить. ::: Чтобы восстановить покупку без использования [Paywall Builder](adapty-paywall-builder) для кастомизации пейвола, вызовите метод `.restorePurchases()`: ```kotlin showLineNumbers Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); if (profile != null) { AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // successful access restore } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | |---------|-----------| | **Profile** |

Объект [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Содержит информацию об уровнях доступа, подписках и разовых покупках.

Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.

| :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-android --- --- title: "Реализация режима Observer в Android SDK" description: "Реализуйте режим Observer в Adapty для отслеживания событий подписки пользователей в Android SDK." --- Если у вас уже есть собственная инфраструктура для покупок и вы не готовы полностью переходить на Adapty, вы можете воспользоваться [режимом Observer](observer-vs-full-mode). В базовом варианте режим Observer обеспечивает расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это вам подходит, нужно сделать всего два шага: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в `true`. Следуйте инструкциям по настройке для [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk). 2. [Передать транзакции](report-transactions-observer-mode-android) из вашей существующей инфраструктуры покупок в Adapty. ## Настройка режима Observer \{#observer-mode-setup\} Включите режим Observer, если вы самостоятельно обрабатываете покупки и управляете статусом подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме Observer Adapty SDK не закрывает транзакции, поэтому убедитесь, что вы обрабатываете их самостоятельно. ::: ```kotlin showLineNumbers class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ) } ``` ```java showLineNumbers public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ); } ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, управляющее [режимом Observer](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в режиме Observer \{#using-adapty-paywalls-in-observer-mode\} Если вы хотите также использовать пейволы и A/B-тесты Adapty, это возможно — но потребует дополнительной настройки в режиме Observer. Вот что нужно сделать в дополнение к шагам выше: 1. Отображайте пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-android). Для пейволов на Paywall Builder следуйте специальным инструкциям для [Android](android-present-paywall-builder-paywalls-in-observer-mode). 3. [Свяжите пейволы](report-transactions-observer-mode-android) с транзакциями покупок. --- # File: report-transactions-observer-mode-android --- --- title: "Сообщение о транзакциях в режиме Observer Mode в Android SDK" description: "Сообщайте о транзакциях покупок в Observer Mode Adapty для получения аналитики пользователей и отслеживания дохода в Android SDK." --- В режиме Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно вручную сообщать о транзакциях из вашего стора. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать о каждой транзакции — так Adapty сможет её распознать. :::warning **Не пропускайте передачу транзакций!** Если вы не вызываете `reportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это привязывает покупку к пейволу, который её инициировал, и обеспечивает точную аналитику пейволов. ```kotlin showLineNumbers val transactionInfo = TransactionInfo.fromPurchase(purchase) Adapty.reportTransaction(transactionInfo, variationId) { result -> if (result is AdaptyResult.Success) { // success } } ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | --------------------------------------------------------- | | transactionInfo | обязательный | TransactionInfo из покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | опциональный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); Adapty.reportTransaction(transactionInfo, variationId, result -> { if (result instanceof AdaptyResult.Success) { // success } }); ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | --------------------------------------------------------- | | transactionInfo | обязательный | TransactionInfo из покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | опциональный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | В режиме Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно вручную сообщать о транзакциях из стора или восстанавливать их. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `restorePurchases`, чтобы передать информацию о транзакции в Adapty. :::warning **Не пропускайте восстановление покупок!** Если вы не вызываете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, свяжите транзакцию с пейволом, который привёл к покупке, с помощью метода `setVariationId`. Это обеспечивает корректную атрибуцию покупки к соответствующему пейволу для точной аналитики. Этот шаг нужен только при использовании пейволов Adapty. ```kotlin showLineNumbers Adapty.restorePurchases { result -> if (result is AdaptyResult.Success) { // success } } Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (`purchase.getOrderId`) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { // success } }); Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (`purchase.getOrderId`) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | **Передача транзакций** Используйте `restorePurchases`, чтобы сообщить о транзакции в Adapty в Observer Mode, как описано на странице [Восстановление покупок в мобильном коде](android-restore-purchase). :::warning **Не пропускайте передачу транзакций!** Если вы не вызываете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: **Привязка пейволов к транзакциям** SDK Adapty не может определить источник покупок, так как их обрабатываете вы. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в режиме Observer Mode, вам нужно привязать транзакцию из стора к соответствующему пейволу в коде вашего приложения. Важно сделать это правильно до публикации приложения, иначе это приведёт к ошибкам в аналитике. ```kotlin Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Параметры запроса: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (purchase.getOrderId) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (purchase.getOrderId) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Показ пейволов Paywall Builder в режиме Observer в Android SDK" description: "Узнайте, как показывать пейволы в режиме Observer с помощью Paywall Builder от Adapty." --- Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрисовать его в коде мобильного приложения для показа пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [Android — показ флоу и пейволов](android-present-paywalls). :::
Перед началом показа флоу (нажмите, чтобы развернуть) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android). 2. Установите и настройте SDK. Убедитесь, что параметр `observerMode` имеет значение `true`. Следуйте нашим инструкциям для конкретного фреймворка [для Android](sdk-installation-android). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement) в дашборде Adapty. 6. [Загрузите флоу и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.

1. Реализуйте `AdaptyUiObserverModeHandler`. Событие `onPurchaseInitiated` сообщает о том, что пользователь инициировал покупку. В ответ на этот коллбэк вы можете запустить собственный флоу покупки: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, flow, flowView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, flow, flowView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Чтобы обрабатывать восстановление покупок в режиме Observer, переопределите `getRestoreHandler()`. По умолчанию метод возвращает `null`, что означает использование встроенного флоу `Adapty.restorePurchases()`. Чтобы задать собственную реализацию восстановления: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Не забудьте вызывать следующие колбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректной работы флоу, например для отображения загрузчика: | Callback | Описание | | :----------------- |:------------------------------------------------------------------------------------------------------| | onStartPurchase() | Callback следует вызывать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Callback следует вызывать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Необязательный. Callback можно вызывать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Необязательный. Callback можно вызывать, чтобы уведомить AdaptyUI о завершении восстановления покупок.| 2. Чтобы отобразить визуальное флоу на экране устройства, его необходимо сначала настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` Параметры запроса: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **flowConfiguration** | обязательный | Передайте объект `AdaptyUI.FlowConfiguration`, содержащий визуальные данные флоу. Используйте метод `AdaptyUI.getFlowConfiguration(flow)` для его загрузки. Подробнее см. в разделе [Получение конфигурации представления](android-get-pb-paywalls#fetch-the-view-configuration). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит нужные продукты. | | **eventListener** | необязательный | Передайте `AdaptyFlowEventListener` для отслеживания событий флоу. Рекомендуется использовать `AdaptyFlowDefaultEventListener` для упрощения работы. Подробнее см. в разделе [Обработка событий флоу и пейвола](android-handling-events). | | **insets** | необязательный | Отступы вокруг флоу, которые не дают кликабельным элементам скрыться за системными панелями. По умолчанию: `Unspecified` — Adapty настраивает отступы автоматически. См. [Изменение отступов флоу](android-present-paywalls#change-flow-insets). | | **customAssets** | необязательный | Передайте объект `AdaptyCustomAssets`, чтобы заменить изображения и видео во флоу или пейволе во время выполнения. Подробнее см. в разделе [Настройка ресурсов](android-get-pb-paywalls#customize-assets). | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте флоу. Этот резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **observerModeHandler** | обязательный для режима Observer | Реализованный вами на предыдущем шаге объект `AdaptyUiObserverModeHandler`. | :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode-android). Иначе Adapty не сможет определить источник флоу покупки. :::
Before you start presenting paywalls (Click to Expand) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Установите и настройте Adapty SDK. Убедитесь, что параметр `observerMode` установлен в `true`. Обратитесь к нашим инструкциям для конкретных фреймворков [для Android](sdk-installation-android). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте пейволы, назначьте им продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 5. [Создайте плейсменты и назначьте им пейволы](create-placement) в дашборде Adapty. 6. [Получите пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.

1. Реализуйте `AdaptyUiObserverModeHandler`. Событие `onPurchaseInitiated` уведомит вас о том, что пользователь инициировал покупку. В ответ на этот колбэк вы можете запустить свой кастомный флоу покупки: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Чтобы обрабатывать восстановление покупок в режиме Observer, переопределите `getRestoreHandler()`. По умолчанию он возвращает `null`, что означает использование встроенного флоу `Adapty.restorePurchases()`. Чтобы предоставить собственную реализацию восстановления: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Не забудьте вызывать следующие колбэки, чтобы уведомлять AdaptyUI о процессе покупки или восстановления. Это необходимо для корректной работы пейвола, например для отображения загрузчика: | Callback | Description | | :----------------- |:---------------------------------------------------------------------------------------------------------| | onStartPurchase() | Коллбэк нужно вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Коллбэк нужно вызвать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Необязательно. Коллбэк можно вызвать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Необязательно. Коллбэк можно вызвать, чтобы уведомить AdaptyUI о завершении восстановления покупок. | 2. Чтобы отобразить визуальный пейвол на экране устройства, его нужно сначала настроить. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **Products** | опционально | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **ViewConfiguration** | обязательно | Передайте объект `AdaptyViewConfiguration`, содержащий визуальные настройки пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **EventListener** | опционально | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется наследоваться от `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **PersonalizedOfferResolver** | опционально | Чтобы указать персонализированное ценообразование ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая возвращает `true` для `AdaptyPaywallProduct`, если цена продукта персонализирована, и `false` в остальных случаях. | | **TagResolver** | опционально | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Этот резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **ObserverModeHandler** | обязательно для режима Observer | Реализованный вами на предыдущем шаге `AdaptyUiObserverModeHandler`. | | **variationId** | обязательно | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | обязательно |

Для iOS, StoreKit 1: объект [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Для Android: строковый идентификатор (`purchase.getOrderId()`) покупки, где покупка является экземпляром класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.

|
Прежде чем показывать пейволы (нажмите, чтобы развернуть) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Установите и настройте Adapty SDK. Убедитесь, что параметр `observerMode` установлен в `true`. Обратитесь к инструкциям для вашего фреймворка: [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) и [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте пейволы, назначьте им продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 5. [Создайте плейсменты и назначьте им пейволы](create-placement) в дашборде Adapty. 6. [Получите пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.
1. Реализуйте `AdaptyUiObserverModeHandler`. Колбэк `AdaptyUiObserverModeHandler` (`onPurchaseInitiated`) срабатывает, когда пользователь инициирует покупку. Вы можете запустить собственный процесс покупки в ответ на этот колбэк: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Также не забудьте вызывать эти коллбэки в AdaptyUI. Это необходимо для правильной работы пейвола, например для отображения загрузчика: | Callback в Kotlin | Callback в Java | Описание | | :---------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ | | onStartPurchase() | onStartPurchase.invoke() | Callback нужно вызвать, чтобы уведомить AdaptyUI о том, что покупка начата. | | onFinishPurchase() | onFinishPurchase.invoke() | Callback нужно вызвать, чтобы уведомить AdaptyUI о том, что покупка завершена успешно, завершена с ошибкой или отменена. | 2. Чтобы отобразить визуальный пейвол, его необходимо сначала инициализировать. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler, ) //======= OR ======= val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { setEventListener(eventListener) setObserverModeHandler(observerModeHandler) showPaywall( viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver, tagResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler ); //======= OR ======= AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Параметры запроса: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Products** | опциональный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **ViewConfiguration** | обязательный | Передайте объект `AdaptyViewConfiguration`, содержащий визуальные параметры пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **Insets** | обязательный | Задайте объект `AdaptyPaywallInsets` с информацией об области, перекрываемой системными панелями, чтобы создать вертикальные отступы для контента. Если строка состояния и панель навигации не перекрывают `AdaptyPaywallView`, передайте `AdaptyPaywallInsets.NONE`. В полноэкранном режиме, когда системные панели перекрывают часть интерфейса, получите отступы так, как показано под таблицей. | | **EventListener** | опциональный | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется расширить `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **PersonalizedOfferResolver** | опциональный | Чтобы указать персонализированную цену ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая сопоставляет `AdaptyPaywallProduct` со значением `true`, если цена продукта персонализирована, иначе — `false`. | | **TagResolver** | опциональный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Этот резолвер принимает параметр тега и преобразует его в соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **ObserverModeHandler** | обязательный для режима Observer | `AdaptyUiObserverModeHandler`, реализованный на предыдущем шаге. | | **variationId** | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | обязательный |

Для iOS, StoreKit 1: объект [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Для Android: строковый идентификатор (`purchase.getOrderId()`) покупки, где покупка — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.

| Для полноэкранного режима, в котором системные панели перекрывают часть интерфейса, получайте отступы следующим образом: ```kotlin showLineNumbers import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom) paywallView.setEventListener(eventListener) paywallView.setObserverModeHandler(observerModeHandler) paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver) } ``` ```java showLineNumbers import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom); paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver); return insets; }); ``` Возвращает: | Объект | Описание | | :------------------ | :------------------------------------------------- | | `AdaptyPaywallView` | объект, представляющий запрошенный экран пейвола. | :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode-android). Иначе Adapty не сможет определить, с какого пейвола была совершена покупка. :::
--- # File: android-troubleshoot-purchases --- --- title: "Troubleshoot purchases in Android SDK" description: "Troubleshoot purchases in Android SDK" --- Этот гайд поможет вам решить распространённые проблемы при реализации покупок вручную через Android SDK. ## makePurchase завершается успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: Метод `makePurchase` выполняется успешно, однако профиль пользователя и статус подписки в Adapty не обновляются. **Причина**: Как правило, это указывает на неполную настройку Google Play Store или ошибки конфигурации. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: Метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: Обычно это происходит, когда процесс покупки запускается несколько раз из-за проблем с управлением состоянием UI или быстрых повторных действий пользователя. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме Observer \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: При использовании `makePurchase` в режиме observer возникает ошибка `AdaptyError.cantMakePayments`. **Причина**: В режиме observer покупки должны обрабатываться на вашей стороне — метод `makePurchase` из Adapty использовать не следует. **Решение**: Если вы используете `makePurchase` для обработки покупок, отключите режим observer. Вам нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме observer. Подробнее см. в разделе [Реализация режима Observer](implement-observer-mode-android). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: Вы получаете ошибку о недоступности биллинга от Google Play Store. **Причина**: Эта ошибка не связана с Adapty. Это ошибка Google Play Billing Library, которая означает, что биллинг недоступен на устройстве. **Решение**: Эта ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## Not found makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Возникают проблемы — `makePurchasesCompletionHandlers` не найден. **Причина**: Как правило, это связано с проблемами при тестировании в песочнице. **Решение**: Создайте нового пользователя в песочнице и повторите попытку. Обычно это решает проблемы с обработчиком завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: Вы сталкиваетесь с другими проблемами при покупках, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](android-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: android-identifying-users --- --- title: "Идентификация пользователей в Android SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта подписки (Android)." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. Пользователей можно искать по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [server-side API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Установка customer user ID при конфигурации \{#setting-customer-user-id-on-configuration\} Если у вас есть идентификатор пользователя на этапе конфигурации, передайте его в параметре `customerUserId` метода `.activate()`: ```kotlin showLineNumbers Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID") ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Установка идентификатора пользователя после конфигурации \{#setting-customer-user-id-after-configuration\} Если у вас нет идентификатора пользователя при конфигурации SDK, вы можете задать его позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации, когда пользователь переходит из анонимного состояния в аутентифицированное. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> if (error == null) { // successful identify } } ``` ```java showLineNumbers Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных пользовательских данных В некоторых случаях, например когда пользователь снова входит в аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких ситуациях Adapty SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — эти данные нужно повторно отправить для идентифицированного пользователя. Также важно помнить, что после идентификации пользователя следует повторно запросить все пейволы и продукты, так как данные нового пользователя могут отличаться. ::: ### Выход и вход в систему \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта в любой момент, вызвав метод `.logout()`: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` После этого вы можете войти снова, используя метод `.identify()`. ### Обнаружение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: android-setting-user-attributes --- --- title: "Установка атрибутов пользователя в Android SDK" description: "Узнайте, как устанавливать атрибуты пользователя в Adapty для улучшения сегментации аудитории." --- Вы можете задавать необязательные атрибуты пользователя вашего приложения: email, номер телефона и другие. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы установить атрибуты пользователя, вызовите метод `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } ``` ```java showLineNumbers AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(new AdaptyProfile.Date(1970, 1, 3)); Adapty.updateProfile(builder.build(), error -> { if (error != null) { // handle the error } }); ``` Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, не сбрасываются. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `` для `AdaptyProfileParameters.Builder` и соответствующие значения ``: | Ключ | Значение | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты, обычно связанные с использованием вашего приложения. Например, для фитнес-приложений это может быть количество тренировок в неделю, а для приложений по изучению языков — уровень знаний пользователя. Такие атрибуты можно использовать в сегментах для создания таргетированных пейволов и офферов, а также в аналитике для выявления продуктовых метрик, которые больше всего влияют на выручку. ```kotlin showLineNumbers builder.withCustomAttribute("key1", "value1") ``` ```java showLineNumbers builder.withCustomAttribute("key1", "value1"); ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```kotlin showLineNumbers builder.withRemovedCustomAttribute("key2") ``` ```java showLineNumbers builder.withRemovedCustomAttribute("key2"); ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на пользователя. - Длина имени ключа — до 30 символов. Имя ключа может содержать буквенно-цифровые символы и любой из следующих: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: android-listen-subscription-changes --- --- title: "Проверка статуса подписки в Android SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем Android-приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка. Прежде чем начать проверку статуса подписки, настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn). ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Рекомендуем получать профиль при запуске приложения — например, когда вы [идентифицируете пользователя](android-identifying-users#setting-customer-user-id-on-configuration) — и обновлять его при каждом изменении. Так вы сможете использовать объект профиля без повторных запросов к серверу. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Прослушивание обновлений профиля, включая уровни доступа](android-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | | --------- | ------------------------------------------------------------ | | Profile |

Объект [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Как правило, для определения наличия у пользователя премиум-доступа достаточно проверить только статус уровня доступа профиля.

Метод `.getProfile` возвращает наиболее актуальные данные, поскольку всегда пытается обратиться к API. Если по какой-либо причине (например, из-за отсутствия интернета) SDK Adapty не может получить данные с сервера, возвращаются данные из кэша. Важно также отметить, что SDK Adapty регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать информацию в актуальном состоянии.

| Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, в новостном приложении с независимыми подписками на разные тематики можно создать уровни доступа «sports» и «science». Однако в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень доступа «premium» по умолчанию. Вот пример проверки уровня доступа «premium» по умолчанию: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium"); if (premium != null && premium.isActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Прослушивание обновлений статуса подписки \{#listening-for-subscription-status-updates\} При каждом изменении подписки пользователя Adapty генерирует событие. Чтобы получать сообщения от Adapty, необходимо выполнить дополнительную настройку: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` ```java showLineNumbers t Adapty.setOnProfileUpdatedListener(profile -> { // handle any changes to subscription state }); ``` Adapty также генерирует событие при запуске приложения. В этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в SDK Adapty, хранит статус подписки профиля. Это означает, что даже при недоступности сервера кэшированные данные позволяют получить информацию о статусе подписки профиля. Однако важно учитывать, что напрямую запросить данные из кэша невозможно. SDK периодически обращается к серверу каждую минуту для проверки обновлений и изменений профиля. При наличии каких-либо изменений — новых транзакций или других обновлений — они отправляются в кэшированные данные, чтобы поддерживать их синхронизацию с сервером. --- # File: kids-mode-android --- --- title: "Режим для детей в Android SDK" description: "Легко включите режим для детей для соответствия политикам Google. GAID и рекламные данные не собираются в Android SDK." --- Если ваше Android-приложение предназначено для детей, вы обязаны соблюдать политики [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими политиками и пройти проверку в сторе. ## Что нужно настроить? \{#whats-required\} Вам нужно настроить SDK, чтобы отключить сбор: - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) - [IP-адреса](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно обращаться с пользовательским ID. ID в формате `` однозначно будет расцениваться как сбор персональных данных — так же, как и использование email. Для режима «Для детей» лучшей практикой является использование случайных или обезличенных идентификаторов (например, хэшированных ID или UUID, сгенерированных на устройстве) — это поможет обеспечить соответствие требованиям. ## Включение режима «Дети» \{#enabling-kids-mode\} ### Обновления в дашборде Adapty В дашборде Adapty нужно отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения Для соблюдения политик необходимо отключить сбор Android Advertising ID (AAID/GAID) и IP-адреса при инициализации Adapty SDK: **Kotlin:** ```kotlin showLineNumbers override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ) } ``` **Java:** ```java showLineNumbers @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ); } ``` ### Обновления в манифесте Android \{#updates-in-your-android-manifest\} :::note Если ваше приложение ориентировано **исключительно** на детскую аудиторию и компилируется под Android 13 (API 33) или выше, Google Play требует не запрашивать разрешение `AD_ID`. Другой SDK в вашем приложении (аналитика, атрибуция или реклама) может добавить это разрешение через слияние манифестов. Установка `withAdIdCollectionDisabled(true)` запрещает Adapty собирать идентификатор, но не удаляет разрешение, объявленное другим SDK. ::: Чтобы удалить разрешение, добавьте следующее внутри элемента `` в файле `app/src/main/AndroidManifest.xml`. Элемент `` должен объявлять `xmlns:tools="http://schemas.android.com/tools"`. ```xml showLineNumbers title="AndroidManifest.xml" ``` --- # File: android-get-onboardings --- --- title: "Получение онбордингов в Android SDK" description: "Узнайте, как получить онбординги в Adapty для Android." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единый Android-стиль, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Paywall Builder на дашборде Adapty, его можно отобразить в вашем Android-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Перед началом убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется как контейнер с конфигурацией, которую приложение должно получить и отобразить. Этот контейнер управляет всем процессом: какой контент показывается, как он представлен и как обрабатываются действия пользователя (например, ответы на вопросы или данные из форм). Контейнер также автоматически отслеживает события аналитики, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности получайте конфигурацию онбординга заранее — чтобы изображения успели загрузиться до того, как пользователь увидит экран. Чтобы получить онбординг, используйте метод `getOnboarding`: ```kotlin showLineNumbers Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // the requested onboarding } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` — английский, `pt-br` — португальский (Бразилия).

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если ваши пользователи часто работают при нестабильном интернете, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные при их наличии. В этом случае пользователи могут получить не самые последние данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии для сокращения сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.

Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Также используется CDN для ускорения загрузки онбордингов и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение последней версии онбордингов при надёжной работе даже при нестабильном интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для данного метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, так как операция может включать несколько запросов под капотом.

Для Android: создать `TimeInterval` можно с помощью функций-расширений (например, `5.seconds`, где `.seconds` берётся из `import com.adapty.utils.seconds`) или `TimeInterval.seconds(5)`. Чтобы снять ограничение, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) со следующими полями: идентификатор и конфигурация онбординга, Remote Config и ряд других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, так что беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а пользователи работают с медленным интернетом, загрузка онбординга может занять дольше, чем хотелось бы. В таких случаях имеет смысл показывать онбординг по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия онбординга. Чтобы решить эту проблему, воспользуйтесь методом `getOnboardingForDefaultAudience`, который получает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг через метод `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Рекомендуем использовать `getOnboarding` вместо `getOnboardingForDefaultAudience`, так как у последнего есть существенные ограничения: - **Проблемы совместимости**: могут возникнуть при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с некорректным отображением в старых версиях. - **Без персонализации**: показывает контент только для аудитории «All Users», то есть таргетинг по стране, атрибуции или пользовательским атрибутам недоступен. Если для вашего случая скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```kotlin Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // Handle successful onboarding retrieval } is AdaptyResult.Error -> { val error = result.error // Handle error case } } } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` означает английский, `pt-br` — бразильский португальский.

Подробнее о кодах локализации и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато время загрузки будет меньше вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию онбордингов, обеспечивая надёжность даже при нестабильном интернет-соединении.

| --- # File: android-present-onboardings --- --- title: "Отображение онбордингов в Android SDK" description: "Узнайте, как отображать онбординги на Android для эффективного вовлечения пользователей." --- :::tip **Начиная с SDK v4**, вы можете использовать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, привычный внешний вид Android, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Если вы настроили онбординг с помощью Onboarding Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой онбординг содержит как то, что должно быть показано, так и то, как это должно быть показано. Чтобы отобразить визуальный онбординг на экране устройства, его необходимо сначала настроить. Для этого вызовите метод `AdaptyUI.getOnboardingView()` или создайте `OnboardingView` напрямую: ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener); ``` ```xml ``` После успешного создания view вы можете добавить его в иерархию представлений и отобразить на экране устройства. Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfig** | обязательный | Конфигурация онбординга, полученная из `AdaptyUI.getOnboardingConfiguration()` | | **eventListener** | обязательный | Реализация `AdaptyOnboardingEventListener` для обработки событий онбординга. Подробнее см. в разделе [Обработка событий онбординга](android-handle-onboarding-events). | ## Изменение цвета индикатора загрузки \{#change-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml ``` ## Добавьте плавные переходы между сплэш-экраном и онбордингом \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} По умолчанию между сплэш-экраном и онбордингом отображается экран загрузки, пока онбординг полностью не загрузится. Однако если вы хотите сделать переход плавнее, это можно настроить: либо продлить сплэш-экран, либо показать что-то другое. Для этого создайте файл `adapty_onboarding_placeholder_view.xml` в папке `res/layout` и определите там плейсхолдер (то, что будет отображаться во время загрузки онбординга). Если вы определите плейсмент, онбординг загрузится в фоне и автоматически отобразится, когда будет готов. ## Отключение отступов безопасной зоны \{#disable-safe-area-paddings\} По умолчанию представление онбординга автоматически применяет отступы безопасной зоны, чтобы избежать перекрытия системными элементами интерфейса — строкой состояния и панелью навигации. Если вы хотите отключить это поведение и полностью управлять разметкой самостоятельно, установите параметр `safeAreaPaddings` в значение `false`. ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener, safeAreaPaddings = false ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener, safeAreaPaddings = false ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener, false ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener, false); ``` Кроме того, вы можете управлять этим поведением глобально, добавив булев ресурс в приложение: ```xml false ``` Если `safeAreaPaddings` установлен в `false`, онбординг растянется на весь экран без автоматических отступов — вы получаете полный контроль над компоновкой, и контент онбординга может использовать всё пространство экрана. ## Настройка способа открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} :::important Настройка способа открытия ссылок в онбордингах поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает удобство работы, позволяя пользователям просматривать веб-страницы прямо в приложении, не переключаясь между приложениями. Если вы предпочитаете открывать ссылки во внешнем браузере, настройте это поведение, задав параметру `externalUrlsPresentation` значение `AdaptyWebPresentation.ExternalBrowser`: ```kotlin val onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ) ``` ```java AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding, AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ); ``` --- # File: android-handle-onboarding-events --- --- title: "Обработка событий онбординга в Android SDK" description: "Обработка событий онбординга в Android с помощью Adapty." --- :::tip **Начиная с SDK v4** вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, привычный внешний вид Android, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Онбординги, настроенные через билдер, генерируют события, на которые ваше приложение может реагировать. Ниже описано, как это сделать. Чтобы управлять процессами на экране онбординга в Android-приложении или отслеживать их, реализуйте интерфейс `AdaptyOnboardingEventListener`. ## Пользовательские действия \{#custom-actions\} В конструкторе вы можете добавить **пользовательское** действие к кнопке и назначить ему ID. Затем вы можете использовать этот ID в своём коде и обрабатывать его как пользовательское действие. Например, если пользователь нажмёт кастомную кнопку — скажем, **Login** или **Allow notifications**, — будет вызван метод делегата `onCustomAction` с ID действия из билдера. Вы можете задавать собственные ID, например "allowNotifications". ```kotlin showLineNumbers class YourActivity : AppCompatActivity() { private val eventListener = object : AdaptyOnboardingEventListener { override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) { when (action.actionId) { "allowNotifications" -> { // Request notification permissions } } } override fun onError(error: AdaptyOnboardingError, context: Context) { // Handle errors } // ... other required delegate methods } } ```
Пример события (нажмите, чтобы развернуть) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
## Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. Вам нужно управлять тем, что происходит при закрытии онбординга. Например: :::important Вам нужно управлять тем, что происходит при закрытии онбординга. Например, необходимо прекратить отображение самого онбординга. ::: Например: ```kotlin override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) { // Dismiss the onboarding screen (context as? Activity)?.onBackPressed() } ```
Пример события (нажмите, чтобы развернуть) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
## Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, если хотите открыть пейвол внутри онбординга. Если же нужно открыть пейвол после его закрытия, есть более простой способ — обработайте [`AdaptyOnboardingCloseAction`](#closing-onboarding) и откройте пейвол без использования данных события. ::: Самый удобный подход — сделать ID действия равным ID плейсмента пейвола. Тогда после получения `AdaptyOnboardingOpenPaywallAction` можно сразу использовать ID плейсмента, чтобы получить и открыть пейвол: ```kotlin override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) { // Get the paywall using the placement ID from the action Adapty.getPaywall(placementId = action.actionId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Get the paywall configuration AdaptyUI.getViewConfiguration(paywall) { result -> when(result) { is AdaptyResult.Success -> { val paywallConfig = result.value // Create and present the paywall val paywallView = AdaptyUI.getPaywallView( activity = this, viewConfig = paywallConfig, products, eventListener = paywallEventListener ) // Add the paywall view to your layout binding.container.addView(paywallView) } is AdaptyResult.Error -> { val error = result.error // handle the error } } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } } ```
Пример события (нажмите, чтобы развернуть) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
## Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, вызывается этот метод: ```kotlin override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) { // Handle loading completion } ```
Пример события (нажмите, чтобы раскрыть) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
## События навигации \{#navigation-events\} Метод `onAnalyticsEvent` вызывается при различных аналитических событиях во время флоу онбординга. Объект `event` может быть одного из следующих типов: |Тип | Описание | |------------|-------------| | `OnboardingStarted` | Когда онбординг загружен | | `ScreenPresented` | Когда отображается любой экран | | `ScreenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `SecondScreenPresented` | Когда отображается второй экран | | `UserEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `OnboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, назначьте идентификатор `final` последнему экрану. | | `Unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит `meta`-информацию: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Порядковый номер текущего экрана во флоу | | `totalScreens` | Общее количество экранов во флоу | Пример использования аналитических событий для трекинга: ```kotlin override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) { when (event) { is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> { // Отслеживаем начало онбординга trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> { // Отслеживаем показ экрана trackEvent("screen_presented", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> { // Отслеживаем завершение экрана с ответом пользователя trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> { // Отслеживаем успешное завершение онбординга trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingAnalyticsEvent.Unknown -> { // Обрабатываем неизвестные события trackEvent(event.name, event.meta) } // При необходимости обработайте другие случаи } } ```
Примеры событий (нажмите, чтобы раскрыть) ```javascript // OnboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // ScreenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // ScreenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // SecondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // UserEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // OnboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
--- # File: android-onboarding-input --- --- title: "Обработка данных из онбордингов в Android SDK" description: "Сохраняйте и используйте данные из онбордингов в Android-приложении с помощью Adapty SDK." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единый внешний вид в стиле Android, быструю загрузку и отсутствие зависимости от среды выполнения WebView. Смотрите [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls), чтобы начать работу. ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранить или обработать тип поля в своём коде. Например: ```kotlin override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle single selection } is AdaptyOnboardingStateUpdatedParams.MultiSelect -> { // Handle multiple selections } is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input } is AdaptyOnboardingStateUpdatedParams.DatePicker -> { // Handle date selection } } } ``` Смотрите формат действия [здесь](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/).
Примеры сохранённых данных (формат может отличаться в вашей реализации) ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ```
## Примеры использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](android-setting-user-attributes) с введёнными данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите задать значение этого поля как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (action.elementId) { "name" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Text -> { builder.withFirstName(inputParams.value) } } } "email" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Email -> { builder.withEmail(inputParams.value) } } } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` ### Настройка пейволов на основе ответов \{#customize-paywalls-based-on-answers\} Используя квизы в онбординге, вы можете настраивать пейволы, которые показываете пользователям после завершения онбординга. Например, можно спросить пользователей об их опыте в спорте и показывать разные CTA и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте значимые идентификаторы его вариантам ответов. 2. Обработайте ответы квиза на основе их идентификаторов и [задайте пользователям пользовательские атрибуты](android-setting-user-attributes). ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Handle quiz responses and set custom attributes when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (action.elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.params.value) } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` 3. [Создайте сегменты](segments) для каждого значения кастомного атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](android-paywalls) для плейсмента в коде приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](android-handle-onboarding-events#opening-a-paywall). --- # File: android-sdk-call-order --- --- title: "Порядок вызовов в Android SDK" description: "Избегайте потери премиум-доступа, пропущенной атрибуции и периодических ошибок ADAPTY_NOT_INITIALIZED, вызывая методы Adapty SDK в правильном порядке." --- `Adapty.activate()` должен завершиться до того, как вы вызовете любой другой метод Adapty SDK. До его завершения SDK не имеет состояния. Любой вызов, выполненный до или параллельно с `activate()`, завершится ошибкой [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty.identify()` в этот момент. Не вызывайте пользовательские методы до срабатывания колбэка завершения `identify`. Вызовы, которые идут параллельно с ним, либо возвращают ошибку в своём колбэке, либо применяются к анонимному профилю, созданному при активации. В таком случае атрибуция, MMP-идентификаторы вроде `appsflyer_id` и данные об установке не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работу с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Сначала инициализируйте их и дождитесь колбэков с UID, и только после этого вызывайте `Adapty.activate`. Иначе MMP ID попадёт в кратковременный анонимный профиль и не всегда переносится в идентифицированный. Подробнее об особенностях AppsFlyer см. в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: обязательны для каждого приложения. Активируйте SDK, затем вызывайте его методы. - **Шаги 1 и 3**: нужны только при интеграции MMP или аналитического SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если вы знаете customer user ID в момент запуска приложения, передайте его в `AdaptyConfig.Builder` до вызова `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте MMP или аналитический SDK (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь коллбэка с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` без `customerUserId` | При запуске приложения, после шага 1, если у вас нет customer user ID (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` для каждого MMP | После шага 2, до любых вызовов, инициированных действиями пользователя | Обязательно, чтобы ID от MMP попали в нужный профиль. | | 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | После шага 3 (или шага 2, если MMP нет), перед шагом 5 — только при пути 2b с аутентификацией | Используйте коллбэк завершения. Параллельные вызовы во время `identify` могут попасть в анонимный профиль. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вызываете `identify`; иначе после шага 3 (или шага 2, если MMP нет) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приводит к потере уровня доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и показу пейволов не той аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователь совершил покупку через веб-чекаут (Stripe, Paddle) и затем установил нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете получить customer user ID до запуска приложения (из процесса авторизации или install referrer), передайте его напрямую в `AdaptyConfig.Builder`. В противном случае веб-покупка останется невидимой на устройстве до тех пор, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Какие метаданные передавать при каждом веб-чекауте, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: android-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейвола в Android SDK" description: "Надёжная загрузка пейволов в Adapty: тайминг, кэширование и паттерны резервного отображения для Android." --- Надёжная загрузка пейвола на Android решает три задачи: быстрый рендеринг, возврат пейвола с таргетингом по аудитории и корректный фолбэк при медленной сети. Правила ниже описывают тайминг, кэширование и паттерны резервного отображения. :::tip Предполагается, что `Adapty.activate()` и `Adapty.identify()` уже выполнены. См. [Порядок вызовов в Android SDK](android-sdk-call-order). ::: ## Правила и подводные камни \{#rules-and-pitfalls\} | Делать | Не делать | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Предзагружать все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время пакетного запроса. | | Вызывайте `getPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `setOnProfileUpdatedListener`. | Вызывать `getPaywall` в `Application.onCreate()`. | Атрибуция ещё не применилась. Пейвол разрешается по аудитории по умолчанию и молча обходит сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ждать ответа `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или просто закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeout` — в разделе [Получение пейволов и продуктов](fetch-paywalls-and-products-android), о выборе подходящего плейсмента — в разделе [Плейсменты](placements). ## Настройка для слабого интернета \{#tune-for-poor-connectivity\} Для рынков со стабильно слабым интернетом (сельские районы, транспорт, регионы с плохой маршрутизацией): - Устанавливайте `fetchPolicy` в `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Установите `loadTimeout` в 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола на `getProfile`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не тормозила интерфейс. --- # File: android-test --- --- title: "Тест и релиз в Android SDK" description: "Узнайте, как проверить статус подписки в приложении на Android с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё Android-приложение, стоит убедиться, что всё настроено правильно и покупки работают как ожидается. Для этого нужно протестировать как интеграцию SDK, так и сам процесс покупки в песочнице Google Play. ## Тестирование приложения \{#test-your-app\} Подробное руководство по тестированию встроенных покупок, включая тестирование в песочнице и проверку в закрытом треке, см. в нашем [гайде по тестированию](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдитесь по [чеклисту для релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки выполняются и передаются в Adapty - Доступ открывается и восстанавливается корректно - Требования к конфиденциальности и ревью соблюдены --- # File: android-sdk-error-handling --- --- title: "Обработка ошибок в Android SDK" description: "Эффективная обработка ошибок Android SDK с помощью руководства по устранению неполадок Adapty." --- Каждая ошибка, возвращаемая SDK, имеет тип `AdaptyError`. :::tip **Включите подробное логирование перед отладкой.** Большинство ошибок `AdaptyError` оборачивают базовую ошибку Play Billing, сети или бэкенда. При включённом подробном логировании (`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — см. [Логирование](sdk-installation-android#logging)) эта ошибка выводится в консоль, что обычно указывает на реальную причину. ::: :::important Если эти решения не помогли, перейдите в раздел [Другие проблемы](#other-issues) и выполните описанные шаги перед обращением в поддержку — это поможет нам быстрее разобраться в ситуации. ::: | Ошибка | Решение | |----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | UNKNOWN | Неизвестная или непредвиденная ошибка. | | [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | Ошибка чаще всего возникает на этапе тестирования. Возможные причины: продукты отсутствуют в продакшене или пользователь не входит в группу тестировщиков в Google Play. | | ADAPTY_NOT_INITIALIZED | SDK Adapty не активирован.
Чаще всего возникает, когда экран-заставка или ранний UI-хук вызывает методы Adapty до завершения `Adapty.activate`. Проблема непостоянна и может не воспроизводиться на эмуляторе из-за отличий в таймингах реального устройства. Дождитесь завершения `Adapty.activate`, прежде чем вызывать другие методы SDK. Полная последовательность описана в [Порядок вызовов в Android SDK](android-sdk-call-order). Также необходимо правильно [настроить Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) с помощью метода `Adapty.activate`. | | PROFILE_WAS_CHANGED | Профиль пользователя изменился во время выполнения операции.
Это происходит, когда метод вызывается в то время, как `Adapty.identify` ещё не завершился — вызов попадает на профиль, который вот-вот будет заменён, и SDK его отклоняет. Дождитесь завершения `Adapty.identify`, прежде чем вызывать другие методы SDK. См. [Порядок вызовов в Android SDK](android-sdk-call-order). | | PRODUCT_NOT_FOUND | Продукт, запрошенный для покупки, недоступен в сторе. | | INVALID_JSON |

JSON резервного пейвола некорректен.

Исправьте дефолтный английский пейвол, затем замените некорректные локальные пейволы. Подробнее об исправлении пейвола — в разделе [Настройка пейвола с помощью Remote Config](customize-paywall-with-remote-config), о замене локальных пейволов — в разделе [Определение резервных пейволов](fallback-paywalls).

| |

CURRENT_SUBSCRIPTION_TO_UPDATE

\_NOT_FOUND_IN_HISTORY

| Исходная подписка, которую нужно заменить, не найдена в активных подписках. | | [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | Запрос достиг максимального таймаута до того, как Google Play успел ответить. Причиной может быть, например, задержка при выполнении действия, запрошенного вызовом Play Billing Library. | | [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | Запрошенная функция не поддерживается Play Store на данном устройстве. | | [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | Соединение клиентского приложения с сервисом Google Play Store через `BillingClient` разорвано. | | [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Сервис Google Play Billing в данный момент недоступен. В большинстве случаев причиной является проблема с сетевым соединением между клиентским устройством и серверами Google Play Billing. | | [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) |

Ошибка биллинга в процессе покупки. Возможные причины:

1. Приложение Play Store на устройстве отсутствует или устарело.

2. Пользователь находится в неподдерживаемой стране.

3. Пользователь входит в корпоративный аккаунт, в котором администратор отключил покупки.

4. Google Play не смог списать средства со способа оплаты пользователя (например, истёк срок действия карты).

5. Пользователь не авторизован в приложении Play Store.

| | [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | API используется некорректно. | | [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Внутренняя ошибка самого Google Play. | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | Продукт уже куплен. | | [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | Запрошенное действие с товаром не выполнено, так как он не принадлежит пользователю. | | [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | Проблема с сетевым соединением между устройством и серверами Play. | | NO_PRODUCT_IDS_FOUND |

Ни один из продуктов пейвола недоступен в сторе.

Если вы столкнулись с этой ошибкой, выполните следующие шаги:

  1. Проверьте, добавлены ли все продукты в дашборд Adapty.
  2. Убедитесь, что **Package name** вашего приложения совпадает с указанным в Google Play Console.
  3. Проверьте, совпадают ли идентификаторы продуктов из сторов с теми, что добавлены в дашборд. Обратите внимание: идентификаторы не должны содержать Bundle ID, если только он не включён в стор.
  4. Убедитесь, что статус платной версии приложения **Active** в налоговых настройках Google. Проверьте актуальность налоговой информации и действительность сертификатов.
  5. Проверьте, привязан ли банковский счёт к приложению — это необходимо для монетизации.
  6. Убедитесь, что продукты доступны в вашем регионе.
  7. Убедитесь, что приложение находится в одном из треков тестирования. Трек **Internal testing** — самый простой вариант: он не требует проверки и скрывает приложение от пользователей.
| | NO_PURCHASES_TO_RESTORE | Google Play не нашёл покупку для восстановления. | | AUTHENTICATION_ERROR | Необходимо правильно [настроить Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) с помощью метода `Adapty.activate`. | | BAD_REQUEST | Некорректный запрос.
Убедитесь, что выполнены все шаги, необходимые для [интеграции с Google Play](google-play-store-connection-configuration). | | SERVER_ERROR | Ошибка сервера. | | REQUEST_FAILED | Сетевая ошибка, которую не удаётся классифицировать точнее. | | DECODING_FAILED | Не удалось декодировать ответ.
Проверьте код и убедитесь, что передаваемые параметры корректны. Например, эта ошибка может означать, что используется недействительный API-ключ. | | ANALYTICS_DISABLED | Обработка аналитических событий невозможна, так как вы [отключили её](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | WRONG_PARAMETER | Один или несколько параметров некорректны: пустое значение там, где оно недопустимо, неверный тип и т. д. | ## Другие проблемы \{#other-issues\} Если вы ещё не нашли решение, можно попробовать следующее: - **Обновление SDK до последней версии**: мы всегда рекомендуем обновляться до последних версий SDK — они более стабильны и содержат исправления известных проблем. - **Обратитесь в службу поддержки или получите помощь от других разработчиков** на [форуме поддержки](https://adapty.featurebase.app/). - **Напишите в поддержку на [support@adapty.io](mailto:support@adapty.io) или через чат**: если вы не готовы обновлять SDK или это не помогло, свяжитесь с нашей командой поддержки. Обратите внимание, что проблема будет решена быстрее, если вы [включите подробное логирование](sdk-installation-android#logging) и поделитесь логами с командой. Также можно приложить соответствующие фрагменты кода. --- # File: migration-to-android-sdk-v4 --- --- title: "Миграция Adapty Android SDK на v. 4.0" description: "Мигрируйте на Adapty Android SDK v4.0, заменив paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty Android SDK 4.0 вводит флоу и переименовывает paywall API соответствующим образом. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткая справка \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `AdaptyUI.getViewConfiguration(paywall)` | `AdaptyUI.getFlowConfiguration(flow, locale)` | | `AdaptyUI.LocalizedViewConfiguration` | `AdaptyUI.FlowConfiguration` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.getPaywallView(...)` | `AdaptyUI.getFlowView(...)` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `AdaptyPaywallScreen` (Compose) | `AdaptyFlowScreen` | | `showPaywall(...)` | `showFlow(...)` | | `AdaptyPaywallInsets` | `AdaptyFlowInsets` | | `AdaptyUiEventListener` | `AdaptyFlowEventListener` | | `AdaptyUiDefaultEventListener` | `AdaptyFlowDefaultEventListener` | | `onPaywallShown` / `onPaywallClosed` | `onFlowShown` / `onFlowClosed` | | `onRenderingError` | `onError` | | `Adapty.updateAttribution(attribution, source)` (`source: String`) | `Adapty.updateAttribution(attribution, source)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key, value)` | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier)` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, а `getPaywallProducts` теперь принимает `AdaptyFlow`. Остальные методы `AdaptyFlowEventListener` (`onProductSelected`, `onPurchaseStarted`, `onPurchaseFinished`, `onPurchaseFailure`, `onRestoreSuccess`, `onRestoreFailure`, `onActionPerformed`, `onAwaitingPurchaseParams`, `onLoadingProductsFailure` и т. д.) сохраняют свои названия и сигнатуры. ## Установка \{#installation\} Укажите версию `adapty-bom` `4.0.0` (или новее) и синхронизируйте проект. BOM автоматически подберёт совместимые версии `android-sdk` и `android-ui`. Инструкции по добавлению зависимостей — в разделе [Установка Adapty SDK](sdk-installation-android). ## Удалённые и устаревшие API \{#removed-and-deprecated-apis\} - **`Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized, callback)`** — удалён. Этот перегруженный метод был помечен как устаревший в v3. Передавайте те же параметры через `AdaptyPurchaseParameters`: ```diff showLineNumbers - Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> /* ... */ } + val params = AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(subscriptionUpdateParams) + .withOfferPersonalized(isOfferPersonalized) + .build() + Adapty.makePurchase(activity, product, params) { result -> /* ... */ } ``` - **Онбординги устарели.** `AdaptyUI.getOnboardingView` и `AdaptyUI.getOnboardingConfiguration` помечены как `@Deprecated` в версии 4.0 — переносите онбординги во флоу, созданные в [Flow Builder](adapty-flow-builder). ## Получение флоу \{#fetching-flows\} ### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration Тип возвращаемого значения при получении данных изменяется с `AdaptyPaywall` на `AdaptyFlow`, а загрузчик конфигурации переименован с `AdaptyUI.getViewConfiguration` на `AdaptyUI.getFlowConfiguration` (возвращает `AdaptyUI.FlowConfiguration` вместо `AdaptyUI.LocalizedViewConfiguration`). Параметр `locale` перемещён из вызова получения данных в `getFlowConfiguration`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> + Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { - val paywall = result.value - if (!paywall.hasViewConfiguration) return@getPaywall - AdaptyUI.getViewConfiguration(paywall) { configResult -> + val flow = result.value + if (!flow.hasViewConfiguration) return@getFlow + AdaptyUI.getFlowConfiguration(flow, locale = "en") { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` теперь принимает `AdaptyFlow`, возвращаемый методом `Adapty.getFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) { result -> /* products */ } + Adapty.getPaywallProducts(flow) { result -> /* products */ } ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow` вместо `AdaptyPaywall`. Событие по-прежнему фиксируется для той же вариации, поэтому существующие метрики воронок и A/B-тестов продолжат работать без изменений в дашборде. ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` Как и в v3, вам не нужно вызывать этот метод при отображении флоу или пейволов, отрендеренных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder) — Adapty отслеживает эти просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### getPaywallView / AdaptyPaywallView → getFlowView / AdaptyFlowView Переименуйте фабричный метод и тип представления, а также передайте `AdaptyUI.FlowConfiguration`: ```diff showLineNumbers - val paywallView = AdaptyUI.getPaywallView( - activity, - viewConfiguration, - products, - eventListener, - ) + val flowView = AdaptyUI.getFlowView( + activity, + flowConfiguration, + products, + eventListener, + ) ``` Если вы создаёте представление напрямую, метод показа также переименован: ```diff showLineNumbers - val paywallView = AdaptyPaywallView(activity) - paywallView.showPaywall(viewConfiguration, products, eventListener) + val flowView = AdaptyFlowView(activity) + flowView.showFlow(flowConfiguration, products, eventListener) ``` В XML-разметке обновите тег представления: ```diff showLineNumbers - + ``` Необязательный параметр `personalizedOfferResolver` удалён из `getFlowView` / `showFlow` / `AdaptyFlowScreen`. Чтобы указать персонализированную цену, задайте её для каждого продукта через `onAwaitingPurchaseParams` (`AdaptyPurchaseParameters.Builder().withOfferPersonalized(true)`). Новый необязательный параметр `customAssets` позволяет переопределять изображения и видео во время выполнения — подробнее см. в разделе [Кастомизация ресурсов](android-get-pb-paywalls#customize-assets). ### AdaptyPaywallScreen → AdaptyFlowScreen В Jetpack Compose переименуйте компонуемый элемент и обновите параметр конфигурации: ```diff showLineNumbers - AdaptyPaywallScreen( - viewConfiguration, + AdaptyFlowScreen( + flowConfiguration, products, eventListener, ) ``` ## Обработка событий \{#handling-events\} Слушатель событий переименован с `AdaptyUiEventListener` на `AdaptyFlowEventListener` (а `AdaptyUiDefaultEventListener` — на `AdaptyFlowDefaultEventListener`). Большинство названий методов не изменились; переименованы только колбэки жизненного цикла и рендеринга: ```diff showLineNumbers - class YourListener : AdaptyUiDefaultEventListener() { + class YourListener : AdaptyFlowDefaultEventListener() { - override fun onPaywallShown(context: Context) {} - override fun onPaywallClosed() {} + override fun onFlowShown(context: Context) {} + override fun onFlowClosed() {} - override fun onRenderingError(error: AdaptyError, context: Context) {} + override fun onError(error: AdaptyError, context: Context) {} } ``` Тела существующих обработчиков менять не нужно — достаточно переименовать тип и переопределения. `onError` срабатывает для тех же ошибок рендеринга, что и `onRenderingError`, плюс для других ошибок времени выполнения, не связанных с покупками. Полный список коллбэков см. в разделе [Обработка событий флоу и пейвола](android-handling-events). В v4 также добавлен колбэк `onBackPressed(context): Boolean`, и его поведение по умолчанию изменилось. Раньше нажатие системной кнопки «Назад» (или жест «назад») передавалось вашей activity или фрагменту, что обычно закрывало пейвол. В v4 реализация по умолчанию перехватывает это нажатие, поэтому **системная кнопка «Назад» больше не закрывает флоу самостоятельно** — аналогично iOS, где флоу нельзя закрыть системным жестом. Предоставьте пользователям явный способ выйти (кнопку **Close** или действие `on_device_back`), либо переопределите `onBackPressed` и верните `false`, чтобы восстановить прежнее поведение. Подробнее см. в разделе [Системная кнопка «Назад»](android-handling-events#system-back-button). Стандартный обработчик покупки также больше не закрывает экран. В v3 стандартный `onPurchaseFinished` закрывал пейвол после любой завершённой покупки, которая не была отменой со стороны пользователя (успешная или ожидающая покупка). В v4 он ничего не делает, поэтому **флоу остаётся открытым после покупки, пока вы сами его не закроете** — поведение совпадает с iOS. Если вы полагались на автоматическое закрытие, закройте экран самостоятельно после завершения покупки. Пример см. в разделе [Успешная, отменённая или ожидающая покупка](android-handling-events#successful-canceled-or-pending-purchase). ## Идентификаторы атрибуции и интеграций \{#attribution-and-integration-identifiers\} ### updateAttribution Параметр `source` меняется с `String` на новый тип `AdaptyAttributionSource`, а `attribution` теперь является `Map` (также доступна перегрузка с `String` в формате JSON). Используйте один из предопределённых источников: ```diff showLineNumbers - Adapty.updateAttribution(attribution, "appsflyer") { error -> /* handle the error */ } + Adapty.updateAttribution(attribution, AdaptyAttributionSource.APPSFLYER) { error -> /* handle the error */ } ``` Предопределённые источники: `AdaptyAttributionSource.APPLE_ADS`, `.ADJUST`, `.APPSFLYER`, `.BRANCH`, `.TENJIN`. Для любого другого источника создайте его из строки: `AdaptyAttributionSource("your_source")`. ### setIntegrationIdentifier `setIntegrationIdentifier(key, value)` заменён методом, который принимает одно или несколько значений `AdaptyIntegrationIdentifier`. Создавайте каждый идентификатор с помощью удобного метода вместо передачи строкового ключа напрямую: ```diff showLineNumbers - Adapty.setIntegrationIdentifier("appsflyer_id", appsFlyerId) { error -> /* handle the error */ } + Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId)) { error -> /* handle the error */ } ``` Можно задать несколько идентификаторов в одном вызове: ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( listOf( AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId), AdaptyIntegrationIdentifier.adjustDeviceId(adjustDeviceId), ) ) { error -> /* handle the error */ } ``` Замените каждую старую строку ключа соответствующим удобным методом: | v3 key | v4 метод `AdaptyIntegrationIdentifier` | |---|---| | `"adjust_device_id"` | `adjustDeviceId(value)` | | `"airbridge_device_id"` | `airbridgeDeviceId(value)` | | `"amplitude_user_id"` | `amplitudeUserId(value)` | | `"amplitude_device_id"` | `amplitudeDeviceId(value)` | | `"appmetrica_device_id"` | `appmetricaDeviceId(value)` | | `"appmetrica_profile_id"` | `appmetricaProfileId(value)` | | `"appsflyer_id"` | `appsflyerId(value)` | | `"branch_id"` | `branchId(value)` | | `"facebook_anonymous_id"` | `facebookAnonymousId(value)` | | `"firebase_app_instance_id"` | `firebaseAppInstanceId(value)` | | `"mixpanel_user_id"` | `mixpanelUserId(value)` | | `"one_signal_subscription_id"` | `oneSignalSubscriptionId(value)` | | `"one_signal_player_id"` | `oneSignalPlayerId(value)` | | `"posthog_distinct_user_id"` | `posthogDistinctUserId(value)` | | `"pushwoosh_hwid"` | `pushwooshHWID(value)` | | `"tenjin_analytics_installation_id"` | `tenjinAnalyticsInstallationId(value)` | Для ключа, которого нет в этом списке, создайте идентификатор напрямую из пользовательского `Key`: `AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("custom"), customValue)`. --- # File: migration-to-android-312 --- --- title: "Миграция Adapty Android SDK на v3.12" description: "Перейдите на Adapty Android SDK v3.12 для повышения производительности и новых возможностей монетизации." --- В Adapty SDK 3.12.0 мы удалили метод `logShowOnboarding` из SDK. Если вы использовали этот метод, он будет недоступен после обновления SDK до версии 3.12 и выше. Вместо этого вы можете [создавать онбординги в конструкторе онбордингов Adapty без кода](onboardings). Аналитика по этим онбордингам отслеживается автоматически, и у вас есть широкие возможности для кастомизации. --- # File: migration-to-android-310 --- --- title: "Гайд по миграции на Android Adapty SDK 3.10.0" description: "" --- Adapty SDK 3.10.0 — это мажорный релиз, который принёс ряд улучшений, однако может потребовать нескольких шагов миграции с вашей стороны: 1. `AdaptyUiPersonalizedOfferResolver` был удалён. Если вы его используете, передайте его в коллбэке `onAwaitingPurchaseParams`. 2. Обновите сигнатуру метода `onAwaitingSubscriptionUpdateParams` для пейволов Paywall Builder. ## Обновление коллбэка параметров покупки \{#update-purchase-parameters-callback\} Метод `onAwaitingSubscriptionUpdateParams` был переименован в `onAwaitingPurchaseParams` и теперь использует `AdaptyPurchaseParameters` вместо `AdaptySubscriptionUpdateParameters`. Это позволяет указывать параметры замены подписки (crossgrade) и отмечать, является ли цена персонализированной ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), а также задавать другие параметры покупки. ```diff showLineNumbers - override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, - ) { - onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) - } + override fun onAwaitingPurchaseParams( + product: AdaptyPaywallProduct, + context: Context, + onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, + ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { + onPurchaseParamsReceived( + AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) + .withOfferPersonalized(true) + .build() + ) + return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked + } ``` Если дополнительные параметры не нужны, можно воспользоваться упрощённым вариантом: ```kotlin showLineNumbers + override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` --- # File: migration-to-android-sdk-34 --- --- title: "Миграция Android SDK Adapty на версию 3.4" description: "Мигрируйте на Android SDK Adapty v3.4 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который содержит улучшения, требующие шагов по миграции с вашей стороны. ## Обновите файлы резервных пейволов \{#update-fallback-paywall-files\} Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](android-use-fallback-paywalls) на новые файлы. ## Обновление реализации Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, обновите его реализацию. В предыдущих версиях требовалось восстанавливать покупки, чтобы Adapty мог распознавать транзакции, совершённые через вашу собственную инфраструктуру, — в Observer Mode у Adapty не было к ним прямого доступа. Если вы использовали пейволы, также нужно было вручную связывать каждую транзакцию с пейволом, который её инициировал. В новой версии вы должны явно сообщать о каждой транзакции, чтобы Adapty её распознала. Если вы используете пейволы, также нужно передавать ID варианта, чтобы связать транзакцию с использованным пейволом. :::warning **Не пропускайте отчёт о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - Adapty.restorePurchases { result -> - if (result is AdaptyResult.Success) { - // success - } - } - - Adapty.setVariationId(transactionId, variationId) { error -> - if (error == null) { - // success - } - } + val transactionInfo = TransactionInfo.fromPurchase(purchase) + + Adapty.reportTransaction(transactionInfo, variationId) { result -> + if (result is AdaptyResult.Success) { + // success + } + } ``` ```diff showLineNumbers - Adapty.restorePurchases(result -> { - if (result instanceof AdaptyResult.Success) { - // success - } - }); - - Adapty.setVariationId(transactionId, variationId, error -> { - if (error == null) { - // success - } - }); + TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); + + Adapty.reportTransaction(transactionInfo, variationId, result -> { + if (result instanceof AdaptyResult.Success) { + // success + } + }); ``` --- # File: migration-to-android330 --- --- title: "Миграция Adapty Android SDK на v3.3" description: "Перейдите на Adapty Android SDK v3.3 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.3.0 — это мажорный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться дополнительные шаги миграции. 1. Обновите способ обработки покупок в пейволах, созданных без Paywall Builder. Перестаньте обрабатывать коды ошибок `USER_CANCELED` и `PENDING_PURCHASE`. Отменённая покупка больше не считается ошибкой и теперь будет отображаться в результатах покупки без ошибок. 2. Замените события `onPurchaseCanceled` и `onPurchaseSuccess` новым событием `onPurchaseFinished` для пейволов, созданных с помощью Paywall Builder. Это изменение связано с той же причиной: отменённые покупки больше не считаются ошибками и будут включены в результаты покупки без ошибок. 3. Измените сигнатуру метода `onAwaitingSubscriptionUpdateParams` для пейволов Paywall Builder. 4. Обновите метод, используемый для предоставления резервных пейволов, если вы передаёте URI файла напрямую. 5. Обновите конфигурации интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase и Google Analytics, Mixpanel, OneSignal, Pushwoosh. ## Обновление процесса покупки \{#update-making-purchase\} Ранее отменённые и ожидающие покупки считались ошибками и возвращали коды `USER_CANCELED` и `PENDING_PURCHASE` соответственно. Теперь для обозначения отменённых, успешных и ожидающих покупок используется новый класс `AdaptyPurchaseResult`. Обновите код покупки следующим образом: ~~~diff Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { - val info = result.value - val profile = info?.profile - - if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) { - // Grant access to the paid features - } + when (val purchaseResult = result.value) { + is AdaptyPurchaseResult.Success -> { + val profile = purchaseResult.profile + if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { + // Grant access to the paid features + } + } + + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash + } + } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ~~~ Полный пример кода смотрите на странице [Совершение покупок в мобильном приложении](android-making-purchases#make-purchase). ## Изменение событий покупки в Paywall Builder \{#modify-paywall-builder-purchase-events\} 1. Добавьте событие `onPurchaseFinished`: ```diff showLineNumbers + public override fun onPurchaseFinished( + purchaseResult: AdaptyPurchaseResult, + product: AdaptyPaywallProduct, + context: Context, + ) { + when (purchaseResult) { + is AdaptyPurchaseResult.Success -> { + // Grant access to the paid features + } + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash) + } + } + } ``` Для полного примера кода ознакомьтесь с разделом [Успешная, отменённая или ожидающая покупка](android-handling-events#successful-canceled-or-pending-purchase) и описанием события. 2. Удалите обработку события `onPurchaseCancelled`: ```diff showLineNumbers - public override fun onPurchaseCanceled( - product: AdaptyPaywallProduct, - context: Context, - ) {} ``` 3. Удалите `onPurchaseSuccess`: ```diff showLineNumbers - public override fun onPurchaseSuccess( - profile: AdaptyProfile?, - product: AdaptyPaywallProduct, - context: Context, - ) { - // Your logic on successful purchase - } ``` ## Изменение сигнатуры метода onAwaitingSubscriptionUpdateParams \{#change-the-signature-of-onawaitingsubscriptionupdateparams-method\} Теперь, если новая подписка приобретается, пока другая ещё активна, вызывайте `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))`, если новая подписка должна заменить текущую активную, или `onSubscriptionUpdateParamsReceived(null)`, если активная подписка должна оставаться активной, а новая — добавиться отдельно: ```diff showLineNumbers - public override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - ): AdaptySubscriptionUpdateParameters? { - return AdaptySubscriptionUpdateParameters(...) - } + public override fun onAwaitingSubscriptionUpdateParams( + product: AdaptyPaywallProduct, + context: Context, + onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, + ) { + onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) + } ``` Смотрите раздел документации [Обновление подписки](android-handling-events#upgrade-subscription) с финальным примером кода. ## Обновление передачи резервных пейволов \{#update-providing-fallback-paywalls\} Если вы передаёте URI файла для предоставления резервных пейволов, обновите этот код следующим образом: ```diff showLineNumbers val fileUri: Uri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback) + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback) ``` ```diff showLineNumbers Uri fileUri = // Получите URI для файла с резервными пейволами - Adapty.setFallbackPaywalls(fileUri, callback); + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback); ``` ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Чтобы интеграции корректно работали с Adapty Android SDK 3.3.0 и выше, обновите конфигурации SDK для следующих интеграций, как описано в разделах ниже. ### Adjust Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - Adjust.getAttribution { attribution -> - if (attribution == null) return@getAttribution - - Adjust.getAdid { adid -> - if (adid == null) return@getAdid - - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error -> - // Handle the error - } - } - } + Adjust.getAdid { adid -> + if (adid == null) return@getAdid + + Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> + if (error != null) { + // Handle the error + } + } + } + + Adjust.getAttribution { attribution -> + if (attribution == null) return@getAttribution + + Adapty.updateAttribution(attribution, "adjust") { error -> + if (error != null) { + // Handle the error + } + } + } ``` ```diff showLineNumbers val config = AdjustConfig(context, adjustAppToken, environment) config.setOnAttributionChangedListener { attribution -> attribution?.let { attribution -> - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error -> + Adapty.updateAttribution(attribution, "adjust") { error -> if (error != null) { // Handle the error } } } } Adjust.onCreate(config) ``` ### AirBridge Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() { override fun onSuccess(result: String) { - val params = AdaptyProfileParameters.Builder() - .withAirbridgeDeviceId(result) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> + if (error != null) { + // Handle the error + } + } } override fun onFailure(throwable: Throwable) { } }) ``` ### Amplitude Обновите код своего мобильного приложения, как показано ниже. Полный пример кода см. в разделе [настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers // For Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId val amplitudeUserId = amplitude.store.userId // - val params = AdaptyProfileParameters.Builder() - .withAmplitudeDeviceId(amplitudeDeviceId) - .withAmplitudeUserId(amplitudeUserId) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceId = result?.deviceId ?: return - val params = AdaptyProfileParameters.Builder() - .withAppmetricaDeviceId(deviceId) - .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID") - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error -> + if (error != null) { + // Handle the error + } + } + + Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> + if (error != null) { + // Handle the error + } + } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { // Handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID)) ``` ### AppsFlyer Обновите код своего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(conversionData: Map) { - Adapty.updateAttribution( - conversionData, - AdaptyAttributionSource.APPSFLYER, - AppsFlyerLib.getInstance().getAppsFlyerUID(context) - ) { error -> - if (error != null) { - // Handle the error - } - } + val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context) + Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.updateAttribution(conversionData, "appsflyer") { error -> + if (error != null) { + // Handle the error + } + } } } ``` ### Branch Обновите код своего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers // Login and update attribution Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> - Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.updateAttribution(data, "branch") { error -> + if (error != null) { + // Handle the error + } + } } } // Logout Branch.getAutoInstance(context).logout() ``` ### Facebook Ads Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers - val builder = AdaptyProfileParameters.Builder() - .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context)) - - Adapty.updateProfile(builder.build()) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier( + "facebook_anonymous_id", + AppEventsLogger.getAnonymousAppDeviceGUID(context) + ) { error -> + if (error != null) { + // Handle the error + } + } ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers // After Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> - Adapty.updateProfile( - AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build() - ) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> + if (error != null) { + // Handle the error + } + } } ``` ```diff showLineNumbers // After Adapty.activate() - FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build(); - - Adapty.updateProfile(params, error -> { - if (error != null) { - // Handle the error - } - }); - }); + FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { + if (error != null) { + // Handle the error + } + }); + }); ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в [настройке SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withMixpanelUserId(mixpanelAPI.distinctId) - .build() - - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { - val params = AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.current.id) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // Handle the error } } } } ``` ```diff showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.getCurrent().getId()) - .build(); - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // Handle the error } }); }; ``` ```diff showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> - val params = AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // Handle the error } - } } } ``` ```diff showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { - AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build(); - - Adapty.updateProfile(params1, error -> { + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // Handle the error } - }); } }; ``` ### Pushwoosh Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().hwid) - .build() - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // Handle the error } } ``` ```diff showLineNumbers - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().getHwid()) - .build(); - - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // Handle the error } }); ``` --- # File: migration-to-android-sdk-v3 --- --- title: "Миграция Adapty Android SDK на v3.0" description: "Мигрируйте на Adapty Android SDK v3.0 для повышения производительности и новых возможностей монетизации." --- Adapty SDK v3.0 добавляет поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Максимальная гибкость и богатые возможности дизайна сделают ваши пейволы эффективнее и прибыльнее. Adapty SDK поставляется как BoM (Bill of Materials), что гарантирует согласованность версий Adapty SDK и AdaptyUI SDK в вашем приложении. Чтобы перейти на v3.0, обновите код следующим образом: ```diff showLineNumbers dependencies { ... - implementation 'io.adapty:android-sdk:2.11.5' - implementation 'io.adapty:android-ui:2.11.3' + implementation platform('io.adapty:adapty-bom:3.0.4') + implementation 'io.adapty:android-sdk' + implementation 'io.adapty:android-ui' } ``` ```diff showLineNumbers dependencies { ... - implementation("io.adapty:android-sdk:2.11.5") - implementation("io.adapty:android-ui:2.11.3") + implementation(platform("io.adapty:adapty-bom:3.0.4")) + implementation("io.adapty:android-sdk") + implementation("io.adapty:android-ui") } ``` ```diff showLineNumbers //libs.versions.toml [versions] .. - adapty = "2.11.5" - adaptyUi = "2.11.3" + adaptyBom = "3.0.4" [libraries] .. - adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" } - adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" } + adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } + adapty = { module = "io.adapty:android-sdk" } + adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... + implementation(libs.adapty.bom) implementation(libs.adapty) implementation(libs.adapty.ui) } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:12.696Z_ _Successfully processed: 41/41 files_