# FLUTTER - 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.733Z Total files: 44 --- # File: sdk-installation-flutter --- --- title: "Установка и настройка Flutter SDK" description: "Пошаговое руководство по установке Adapty SDK на Flutter для приложений с подписками." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше Flutter-приложение: - **Core Adapty**: Основной SDK, необходимый для работы Adapty в вашем приложении. - **AdaptyUI**: Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный no-code инструмент для создания кросс-платформенных пейволов. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Загляните в наше [демо-приложение](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example): оно демонстрирует полную настройку, включая отображение пейволов, совершение покупок и другие базовые функции. ::: ## Требования \{#requirements\} Adapty SDK поддерживает iOS 13.0+, но для корректной работы пейволов, созданных в Paywall Builder, требуется iOS 15.0+. Adapty Flutter SDK 4.0 — с поддержкой [Flow Builder](adapty-flow-builder) — повышает минимальные требования до **iOS 15.0+**, **Xcode 26+** и **Flutter 3.32.0+** (Dart 3.8.0+). Подробности об установке см. в разделе [Adapty SDK 4.0](#adapty-sdk-40-swift-package-manager) ниже. :::info Adapty совместима с Google Play Billing Library версий до 8.x включительно. По умолчанию Adapty работает с Google Play Billing Library v7.0.0, но если вам нужна более поздняя версия, вы можете вручную [добавить зависимость](https://developer.android.com/google/play/billing/integrate#dependency). ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) :::important Шаги ниже устанавливают последнюю стабильную версию SDK (3.x). Если вам нужна v4 — необходимая для [Flow Builder](adapty-flow-builder) и используемая в [быстром старте](flutter-quickstart-paywalls) — следуйте инструкции [Adapty SDK 4.0: Swift Package Manager](#adapty-sdk-40-swift-package-manager) ниже. ::: 1. Добавьте Adapty в файл `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^ ``` 2. Выполните следующую команду для установки зависимостей: ```bash showLineNumbers title="Terminal" flutter pub get ``` 3. Импортируйте Adapty SDK в своё приложение: ```dart showLineNumbers title="main.dart" import 'package:adapty_flutter/adapty_flutter.dart'; ``` ### Adapty SDK 4.0: Swift Package Manager Добавьте Adapty Flutter SDK 4.0 — который добавляет поддержку [Flow Builder](adapty-flow-builder) — в ваш `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` Начиная с v4, нативный iOS SDK больше не распространяется через CocoaPods — плагин подключает его только через **Swift Package Manager** ([репозиторий спецификаций CocoaPods переходит в режим только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). Если вы используете Flutter 3.32–3.43, включите поддержку Swift Package Manager один раз: ```bash showLineNumbers title="Terminal" flutter config --enable-swift-package-manager ``` Flutter 3.44 и более поздние версии включают Swift Package Manager по умолчанию, поэтому никаких дополнительных действий не требуется. Об изменениях API в v4 читайте в [руководстве по миграции](migration-to-flutter-sdk-v4). ## Активация модуля Adapty в SDK Adapty \{#activate-adapty-module-of-adapty-sdk\} Активируйте 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-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. ```dart showLineNumbers title="main.dart" void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { _initializeAdapty(); super.initState(); } Future _initializeAdapty() async { try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), ); } catch (e) { // handle the error } } Widget build(BuildContext context) { return Text("Hello"); } } ``` :::important Дождитесь завершения `activate` перед вызовом любых других методов Adapty SDK. Полная последовательность описана в [порядке вызовов в Flutter SDK](flutter-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), сначала [активируйте модуль AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) ниже, а затем следуйте [быстрому старту с Paywall Builder](flutter-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, обратитесь к [быстрому старту для пользовательских пейволов](flutter-quickstart-manual). ## Активация модуля AdaptyUI в составе Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder) и уже [установили модуль AdaptyUI](sdk-installation-flutter#install-adapty-sdk), его также необходимо активировать: :::note Зависимости, связанные с AdaptyUI, подключаются к вашему приложению независимо от того, активирован ли AdaptyUI. ::: :::important В коде сначала необходимо активировать основной модуль Adapty, и только затем — AdaptyUI. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withActivateUI(true), // This automatically activates AdaptyUI ); ``` ## Необязательная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty логирует ошибки и другую важную информацию, чтобы помочь вам разобраться в происходящем. Доступны следующие уровни логирования: | Уровень | Описание | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------- | | `AdaptyLogLevel.error` | Записываются только ошибки | | `AdaptyLogLevel.warn` | Записываются ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания. | | `AdaptyLogLevel.info` | Записываются ошибки, предупреждения и различные информационные сообщения. Значение по умолчанию | | `AdaptyLogLevel.verbose` | Записывается любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | | `AdaptyLogLevel.debug` | Записывается отладочная информация. | Вы можете задать уровень логирования в приложении до настройки Adapty: ```dart showLineNumbers title="main.dart" // Set log level before activation. // 'verbose' is recommended for development and the first production release await Adapty().setLogLevel(AdaptyLogLevel.verbose); // Or set it during configuration await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withLogLevel(AdaptyLogLevel.verbose), ); ``` ### Политика обработки данных \{#data-policies\} Adapty не хранит персональные данные пользователей, если вы не передаёте их явно. При этом вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA), а также чтобы не собирать лишние данные, если функции на основе IP-адреса вашему приложению не нужны. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withIpAddressCollectionDisabled(true), ); ``` #### Отключение сбора и передачи рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `appleIdfaCollectionDisabled` (iOS) или `googleAdvertisingIdCollectionDisabled` (Android) в значение `true`, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — `false`. Используйте этот параметр для соответствия политикам App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не использует рекламную атрибуцию или аналитику на основе рекламных идентификаторов. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleIdfaCollectionDisabled(true) // iOS ..withGoogleAdvertisingIdCollectionDisabled(true), // Android ); ``` #### Настройка конфигурации медиакэша для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Модуль активируется автоматически вместе с Adapty SDK. Если вы не используете Paywall Builder и хотите отключить модуль AdaptyUI, передайте `withActivateUI(false)` при активации. По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевой нагрузки. Вы можете настроить параметры кэша, передав собственную конфигурацию. Используйте `withMediaCacheConfiguration`, чтобы переопределить ограничения кэша по умолчанию. Это необязательно — если вы не вызываете этот метод, будут применяться значения по умолчанию (100 МБ на диске, неограниченное количество объектов в памяти). Однако если вы создаёте объект конфигурации, все его параметры обязательны. ```dart showLineNumbers title="main.dart" final mediaCacheConfig = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit: 2147483647, // max int value diskStorageSizeLimit: 200 * 1024 * 1024, // 200 MB ); await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withMediaCacheConfiguration(mediaCacheConfig), ); ``` **Параметры:** | Параметр | Наличие | Описание | |-------------------------|----------|-----------------------------------------------------------------------------| | memoryStorageTotalCostLimit | обязательный | Общий размер кэша в памяти в байтах. По умолчанию — 100 МБ. | | memoryStorageCountLimit | обязательный | Максимальное количество элементов в памяти. По умолчанию — максимальное значение int. | | diskStorageSizeLimit | обязательный | Ограничение размера файла на диске в байтах. По умолчанию — 100 МБ. | ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их и на Android, передайте `withGoogleLocalAccessLevelAllowed` со значением `true`: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleLocalAccessLevelAllowed(true), ); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Когда `appleClearDataOnBackup` установлен в `true`, SDK определяет, что приложение восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, детали продуктов и пейволы. После этого SDK инициализируется с чистого состояния. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций с Apple и данные пользователя на серверах Adapty остаются без изменений. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleClearDataOnBackup(true) // default – false ); ``` ## Устранение неполадок \{#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/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `` включает tools: ```xml ... ``` #### 2. Переопределите атрибуты резервного копирования в `` В том же файле `AndroidManifest.xml` обновите тег ``, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml ... ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила 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" #### Покупки не выполняются после возврата из другого приложения на Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или повторно использовать его при возврате пользователя из Google Play, банковского приложения или браузера. Это может привести к потере результата покупки или её трактовке как отменённой. Чтобы покупки работали корректно, используйте только режимы запуска `standard` или `singleTop` для Activity, который инициирует флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающая флоу покупки, настроена на режим `standard` или `singleTop`: ```xml ``` #### Ошибки сборки Swift 6, вызванные переопределением SWIFT_VERSION в Podfile \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} При сборке Flutter-приложения для iOS могут появляться ошибки компиляции Swift 6 в целевых объектах пода Adapty. Типичные симптомы: несоответствия `@Sendable` в `AdaptyUIBuilderLogic`, отсутствие соответствия `Sendable` у типов Adapty или ошибки изоляции акторов. Поды Adapty объявляют `s.swift_version = '6.0'` и требуют Swift 6 для сборки. Код вашего приложения может остаться на Swift 5 — только целевые поды Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) должны собираться с Swift 6. Наиболее частая причина — хук `post_install` в `ios/Podfile`, который перезаписывает `SWIFT_VERSION` для каждого целевого пода: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Исправление**: Исключите pod-таргеты Adapty из переопределения: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Затем выполните `pod install` из директории `ios/` и пересоберите проект. Для проверки откройте `ios/Pods/Pods.xcodeproj`, выберите таргет пода `Adapty` → **Build Settings** → **Swift Language Version**. Там должно быть указано **Swift 6**. --- # File: flutter-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в Flutter SDK" description: "Быстрый старт по включению встроенных покупок с Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно разобраться в трёх ключевых концепциях: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые представляют продукты пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](flutter-quickstart-manual). - [**Плейсменты**](placements) – где и когда показывать флоу в приложении (например, `main`, `onboarding`, `settings`). Вы прикрепляете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в приложении. Выберите подходящий в зависимости от требований вашего приложения: | Реализация | Сложность | Когда использовать | |------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow Builder | ✅ Легко | Вы [создаёте готовый к покупке флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отрисовывает его и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. | | Пейвол, созданный вручную | 🟡 Средне | Вы реализуете интерфейс пейвола в коде приложения, но всё равно получаете объект флоу из Adapty, сохраняя гибкость в управлении продуктами. См. [гайд](flutter-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Обратите внимание, что observer mode имеет ряд ограничений в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете строить UI пейвола самостоятельно, см. [Реализация пейволов вручную](flutter-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде приложения нужно сделать всего три вещи: 1. **Получить флоу**: запросить его из Adapty. 2. **Показать его — покупки Adapty обработает сам**: отобразить представление в приложении. 3. **Обработать действия кнопок**: связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии кнопок. ## Перед началом работы \{#before-you-start\} Прежде чем приступить, выполните следующие шаги: 1. Подключите приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-flutter) в коде приложения. В этом гайде используются API Adapty Flutter 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` и проверить, был ли он создан в билдере, используя свойство `hasViewConfiguration`. 2. Создать отображение флоу с помощью метода `createFlowView`. Отображение содержит элементы UI и стили, необходимые для показа флоу. :::important Чтобы получить конфигурацию вида, необходимо включить переключатель **Show on device** в билдере. В противном случае вы получите пустую конфигурацию вида, и флоу не отобразится. ::: ```dart showLineNumbers try { // the requested flow final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final view = await AdaptyUI().createFlowView( flow: flow, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 2. Отобразите флоу \{#display-the-flow\} Теперь, когда у вас есть объект флоу, достаточно добавить несколько строк, чтобы его отобразить. Для отображения флоу вызовите метод `view.present()` на объекте `view`, созданном методом `createFlowView`. Каждый `view` можно показать только один раз: после закрытия он освобождается из памяти. Если нужно показать флоу снова, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Подробнее о том, как отобразить флоу, читайте в нашем [гайде](flutter-present-paywalls). ::: ## 3. Обработка действий кнопок \{#3-handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Flutter SDK автоматически обрабатывает покупки, восстановление, закрытие экрана и открытие URL. Однако у других кнопок есть пользовательские или предустановленные ID, и обработку таких действий нужно реализовать в вашем коде. Чтобы управлять процессами на экране флоу или отслеживать их, реализуйте методы `AdaptyUIFlowsEventsObserver` и установите наблюдатель до показа любого экрана. Если пользователь выполнил какое-либо действие, будет вызван `flowViewDidPerformAction`, и ваше приложение должно отреагировать в зависимости от ID действия. Три метода наблюдателя **обязательны**: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` и `flowViewDidReceiveError` — без них класс не скомпилируется. :::tip Читайте наши гайды по обработке [действий](flutter-handle-paywall-actions) и [событий](flutter-handling-events) кнопок. ::: Реализуйте наблюдатель как отдельный долгоживущий объект, а не виджет. Поскольку во всём приложении используется единственный глобальный слот для наблюдателя, привязка его к `State` приведёт к утечке экрана (SDK хранит на него сильную ссылку) и молчаливой замене при регистрации следующего экрана. Использование `extends` также наследует поведение SDK по умолчанию, поэтому помимо трёх обязательных методов достаточно переопределить только нужные коллбэки. ```dart showLineNumbers title="Flutter" // A dedicated, long-lived handler for flow events. // It does NOT live inside a Widget/State, so it never leaks and is never // silently replaced when screens are pushed or popped. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // This method is called when user performs an action on the flow UI. // Overriding it replaces the default behavior (dismiss on close, open URLs), // so keep those cases if you want to preserve it. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } ``` Зарегистрируйте обработчик **один раз** при запуске приложения, до отображения любого флоу: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](flutter-check-subscription-status), чтобы показывать пейвол или открывать доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно объединить в вашем приложении. ```dart void main() { // Register a single, long-lived observer once, before any flow is shown. // It is intentionally a plain object (NOT a Widget/State): its lifetime is the // whole app, so it never leaks and is never silently replaced when screens are // pushed or popped. AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); runApp(MaterialApp(home: FlowScreen())); } /// A dedicated handler for AdaptyUI flow events. /// /// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented /// by a `State`), which gives you two things for free: /// * the SDK's sensible defaults for optional callbacks, so besides the three /// required methods you only override what you actually care about; /// * a lifecycle that is independent of the widget tree — there is no strong /// reference back into a `Widget`, so nothing leaks and there is nothing to /// unregister. /// /// Every callback receives the [AdaptyUIFlowView] it relates to, so handling /// flow actions never requires a `BuildContext` or widget state. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // Called when the user performs an action on the flow UI. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): // Open the URL natively, honoring the dashboard browser setting. AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes. @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds. @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors. @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } class FlowScreen extends StatefulWidget { const FlowScreen({super.key}); @override State createState() => _FlowScreenState(); } class _FlowScreenState extends State { @override void initState() { super.initState(); _showFlowIfNeeded(); } Future _showFlowIfNeeded() async { try { final flow = await Adapty().getFlow( placementId: 'YOUR_PLACEMENT_ID', ); if (!flow.hasViewConfiguration) return; final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); } catch (_) { // Handle any errors (network, SDK issues, etc.) } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Adapty Flow Example')), body: Center( // Add a button to re-trigger the flow for testing purposes. child: ElevatedButton( onPressed: _showFlowIfNeeded, child: const Text('Show Flow'), ), ), ); } } ``` --- # File: flutter-check-subscription-status --- --- title: "Проверка статуса подписки во Flutter SDK" description: "Узнайте, как проверить статус подписки в приложении на Flutter с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или нужно показать ему пейвол, необходимо проверить его [уровень доступа](access-level) в профиле. В этой статье показано, как получить данные профиля и решить, что показать пользователю — пейвол или платный контент. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Есть два варианта: - Вызвать `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или требуется принудительное обновление. - Настроить **автоматическое обновление профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. ### Получение профиля \{#get-profile\} Самый простой способ узнать статус подписки — вызвать метод `getProfile`: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `Adapty().didUpdateProfileStream.listen()` для отслеживания изменений профиля — Adapty автоматически вызывает этот метод при изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при каждом вызове этого метода, чтобы использовать их в приложении без лишних сетевых запросов. ```dart class SubscriptionManager { AdaptyProfile? _currentProfile; SubscriptionManager() { // Listen for profile updates Adapty().didUpdateProfileStream.listen((profile) { _currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() bool hasAccess() { return _currentProfile?.accessLevels['premium']?.isActive ?? false; } } ``` :::note Adapty автоматически вызывает слушатель потока обновлений профиля при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно принимать мгновенные решения о показе пейволов или предоставлении доступа к платным функциям, можно напрямую проверить профиль пользователя. Это удобно, например, при запуске приложения, при переходе в премиум-разделы или перед показом определённого контента. ```dart Future _checkAccessLevel() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false; } catch (e) { print('Error checking access level: $e'); return false; // Show paywall if access check fails } } Future _initializePaywall() async { await _loadPaywall(); final hasAccess = await _checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } ``` ## Следующие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](flutter-quickstart-identify), чтобы они всегда имели доступ к тому, за что заплатили. --- # File: flutter-quickstart-identify --- --- title: "Идентификация пользователей в Flutter SDK" description: "Быстрый старт по настройке Adapty для управления встроенными покупками в Flutter." --- :::important Этот гайд для тех, у кого есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации в вашем приложении: - Если приложение не использует серверную аутентификацию и не хранит данные пользователей, см. [раздел об анонимных пользователях](#anonymous-users). - Если приложение использует (или будет использовать) серверную аутентификацию, см. [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — это сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой аутентификации. Вот в чём разница между анонимными и идентифицированными пользователями: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | Сохранение истории покупок на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Хранение данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#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) должны быть уникальными для каждого пользователя. Если указать одно и то же значение, все пользователи будут считаться одним. ::: Всегда используйте `await` для `identify` перед вызовом других методов SDK. Параллельные вызовы приводят к ошибке `#3006 profileWasChanged` или работе с анонимным профилем. См. [Порядок вызовов во Flutter SDK](flutter-sdk-call-order). ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Уникален для каждого пользователя } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### При активации SDK \{#during-the-sdk-activation\} Если вы уже знаете customer user ID в момент активации SDK, передайте его прямо в метод `activate` — вызывать `identify` отдельно не нужно. Если вы знаете customer user ID, но передаёте его только после активации, это значит, что при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (ранее уже использованный), так и новый. Если передать новый, то профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на дашборды аналитики, так как установки считаются по идентификаторам устройств. Идентификатор устройства соответствует одной установке приложения из стора и пересоздаётся только после переустановки. Он не зависит от того, является ли установка первой или повторной, и от того, используется ли существующий пользовательский идентификатор. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без переустановки не генерируют дополнительные события установки. Если вы хотите считать установки по уникальным пользователям, а не по устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```dart showLineNumbers" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. ); } catch (e) { // handle the error } ``` ### Выход пользователей из системы \{#log-users-out\} Если в вашем приложении есть кнопка выхода, используйте метод `logout`. :::important Выход из системы создаёт новый анонимный профиль для пользователя. ::: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::info Чтобы снова авторизовать пользователя в приложении, используйте метод `identify`. ::: ### Разрешить покупки без входа \{#allow-purchases-without-login\} Если ваши пользователи могут совершать покупки как до, так и после входа в приложение, необходимо убедиться, что после входа они сохранят доступ: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, и вся история покупок сохраняется. - Если это существующий customer user ID (customer user ID уже привязан к профилю), после смены профиля нужно получить актуальный уровень доступа. Для этого можно либо вызвать [`getProfile`](flutter-check-subscription-status) сразу после идентификации, либо [подписаться на обновления профиля](flutter-check-subscription-status), чтобы данные синхронизировались автоматически. ## Дальнейшие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше пользы, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](flutter-onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Интегрируйтесь с сервисами маркетинговой атрибуции и аналитики всего в одну строку кода - [**Настройка пользовательских атрибутов профиля**](flutter-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-flutter --- --- title: "Интеграция Adapty в Flutter-приложение с помощью навыка SDK integration" description: "Используйте навык adapty-sdk-integration для полноценной интеграции Adapty SDK в ваше Flutter-приложение с помощью 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-flutter) — оно проведёт ваш 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-flutter --- --- title: "Интеграция Adapty во Flutter-приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty во Flutter-приложение с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд поможет шаг за шагом интегрировать Adapty в Flutter-приложение с помощью инструмента 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**. Подключите App Store и Google Play, если ваше Flutter-приложение поддерживает обе платформы. Это обязательное условие для работы покупок. [Подключить сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигурацию Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. В коде продукты не указываются напрямую — Adapty передаёт их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: в дашборде Adapty создайте пейвол на странице **Paywalls**, затем назначьте его на плейсмент на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `Adapty().getPaywall()`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels['premium']?.isActive`. Уровень доступа `premium` по умолчанию подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Когда у вас есть все пять, можно писать код. Скажите своему LLM: «Мой публичный SDK-ключ — X, идентификатор плейсмента — 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 автоматически загружает нужные документы исходя из вашего запроса — никаких ручных вставок ссылок. 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 Flutter SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вставлять ссылки на документацию вручную, порядок реализации важен. Следуйте [пошаговому руководству](#implementation-walkthrough) ниже, выполняя шаги строго по порядку. ::: ### Используйте документацию в формате обычного текста \{#use-plain-text-docs\} Любую документацию Adapty можно открыть как обычный текст в формате Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-flutter.md](https://adapty.io/docs/ru/adapty-cursor-flutter.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): вы создаёте пейволы в no-code редакторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](flutter-making-purchases): вы строите собственный интерфейс пейвола в коде, но всё равно используете Adapty для получения продуктов и обработки покупок. - [**Режим Observer**](observer-vs-full-mode): вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в разделе быстрого старта](flutter-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK с помощью `flutter pub add` и активируйте её с вашим публичным ключом SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-flutter) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-flutter.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается на iOS и Android. В консоли отладки есть лог активации Adapty. - **Частая проблема:** «Public API key is missing» → проверьте, что вы заменили плейсхолдер на реальный ключ из **App settings**. ::: ### Показ пейволов и обработка покупок \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). **Гайды:** - [Включить покупки через пейволы (быстрый старт)](flutter-quickstart-paywalls) - [Получить пейволы Paywall Builder и их конфигурацию](flutter-get-pb-paywalls) - [Отобразить пейволы](flutter-present-paywalls) - [Обработать события пейвола](flutter-handling-events) - [Реагировать на действия кнопок](flutter-handle-paywall-actions) Отправь это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-paywalls.md - https://adapty.io/docs/ru/flutter-get-pb-paywalls.md - https://adapty.io/docs/ru/flutter-present-paywalls.md - https://adapty.io/docs/ru/flutter-handling-events.md - https://adapty.io/docs/ru/flutter-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидается:** Пейвол отображается с настроенными продуктами. Нажатие на продукт запускает диалог покупки в песочнице. - **Частая ошибка:** Пустой пейвол или ошибка `getPaywall` → проверьте, что ID плейсмента точно совпадает с указанным в дашборде, и что плейсменту назначена аудитория. ::: **Гайды:** - [Включить покупки в вашем кастомном пейволе (быстрый старт)](flutter-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-flutter) - [Отобразить пейвол на основе Remote Config](present-remote-config-paywalls-flutter) - [Совершать покупки](flutter-making-purchases) - [Восстановить покупки](flutter-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-flutter.md - https://adapty.io/docs/ru/present-remote-config-paywalls-flutter.md - https://adapty.io/docs/ru/flutter-making-purchases.md - https://adapty.io/docs/ru/flutter-restore-purchase.md :::tip[Checkpoint] - **Ожидаемый результат:** Ваш пользовательский пейвол отображает продукты, полученные из Adapty. Нажатие на продукт открывает диалог покупки в песочнице. - **Частая проблема:** Пустой массив продуктов → убедитесь, что в дашборде пейволу назначены продукты, а у плейсмента есть аудитория. ::: **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-flutter) - [Отправка транзакций в Observer mode](report-transactions-observer-mode-flutter) Отправьте это своему 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-flutter.md - https://adapty.io/docs/ru/report-transactions-observer-mode-flutter.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Подводный камень:** Если событий нет — убедитесь, что вы передаёте транзакции в Adapty и что серверные уведомления настроены для обоих сторов. ::: ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы ограничить доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](flutter-check-subscription-status) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels['premium']?.isActive` возвращает `true`. - **Частая проблема:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет авторизации. ::: **Гайд:** [Идентификация пользователей](flutter-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty().identify()` в разделе **Profiles** дашборда отображается ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до загрузки пейволов — иначе события могут привязаться к анонимному профилю. ::: ### Подготовка к релизу Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключение сторов, серверные уведомления, флоу покупки, проверки уровня доступа и требования конфиденциальности. - **Возможная проблема:** Отсутствуют серверные уведомления → настройте App Store Server Notifications в **App settings → iOS SDK** и 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. Обратите внимание, что для некоторых AI-агентов (например, ChatGPT) нужно скачать `llms.txt` и загрузить файл в чат. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой — используйте только когда нужна полная картина. - Flutter-специфичные [`flutter-llms.txt`](https://adapty.io/docs/ru/flutter-llms.txt) и [`flutter-llms-full.txt`](https://adapty.io/docs/ru/flutter-llms-full.txt): Подмножества документации для конкретной платформы, позволяющие сэкономить токены по сравнению с полным сайтом. --- # File: flutter-get-pb-paywalls --- --- title: "Получение флоу и пейволов — Flutter" description: "Получите флоу и пейволы из Adapty в вашем Flutter-приложении." --- После того как вы [разработали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, вместе с конфигурацией его отображения, как описано ниже. Обратите внимание, что этот раздел посвящён флоу и пейволам, созданным в Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-flutter). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать отображать флоу и пейволы в вашем мобильном приложении (нажмите, чтобы раскрыть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-flutter) в своём мобильном приложении.
## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения. Такой флоу или пейвол содержит всё необходимое: и то, что должно отображаться, и то, как это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, настроить конфигурацию отображения и затем показать его в мобильном приложении. Загрузите флоу или пейвол и создайте его [представление](flutter-get-pb-paywalls#fetch-the-view-configuration) как можно раньше — желательно задолго до его отображения. Метод `createFlowView` загружает конфигурацию представления и начинает скачивать и кешировать изображения в фоне. Чем раньше вы его вызовете, тем больше времени останется на завершение загрузок. К моменту показа флоу или пейвола его конфигурация и изображения уже могут быть закешированы и готовы к отображению. Чтобы получить флоу или пейвол, используйте метод `getFlow`: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // запрошенный флоу/пейвол } on AdaptyError catch (adaptyError) { // обработка ошибки } catch (e) { // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

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

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

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

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

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

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

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

| ## Параметры ответа \{#response-parameters\} | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`instanceIdentity`, `variationId`), именем, плейсментом, вариантами пейволов (`paywalls`) и Remote Config (`remoteConfigs`). | ## Получение конфигурации экрана \{#fetch-the-view-configuration\} :::important Убедитесь, что в конструкторе включён переключатель **Show on device**. Если он не активирован, конфигурация экрана не будет доступна для получения. ::: Если плейсмент был создан в **Flow Builder** или **Paywall Builder**, Adapty самостоятельно отрисовывает UI — свойство `hasViewConfiguration` полученного флоу равно `true`. Создайте представление с помощью `createFlowView`, затем [отобразите флоу или пейвол](flutter-present-paywalls). Если плейсмент — это кастомный пейвол без UI Builder (`hasViewConfiguration` равно `false`), [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-flutter). :::warning Результат метода `createFlowView` можно использовать для отображения только один раз. Если нужно показать его снова, вызовите метод `createFlowView` заново. ::: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView(flow: flow); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow` для получения представления нужного флоу/пейвола. | | **customTags** | необязательный | Задайте карту пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в контенте и динамически заменяются конкретными строками для персонализации контента во флоу/пейволе. Подробнее см. в разделе [Пользовательские теги в Paywall Builder](custom-tags-in-paywall-builder). | | **preloadProducts** | необязательный | Включите, чтобы оптимизировать время отображения продуктов на экране. При значении `true` AdaptyUI автоматически загрузит необходимые продукты. По умолчанию: `false`. | | **loadTimeout** | необязательный | Значение типа `Duration`, ограничивающее время загрузки конфигурации представления. При истечении таймаута будут использованы кешированные данные или локальный резервный вариант. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию флоу](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](flutter-localizations-and-locale-codes). ::: После того как вы получили представление, [отобразите флоу/пейвол](flutter-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). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

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

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

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

| ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в своём флоу/пейволе, используйте кастомные ассеты. У изображений-героев и видео-героев есть предопределённые ID: `hero_image` и `hero_video`. В бандле кастомных ассетов вы обращаетесь к этим элементам по их ID и настраиваете их поведение. Для остальных изображений и видео необходимо [задать кастомный ID](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разное изображение или видео отдельным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед воспроизведением видео. Вот пример того, как можно передать пользовательские ресурсы через простой словарь: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createFlowView( flow: flow, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Если ресурс не найден, флоу/пейвол вернётся к внешнему виду по умолчанию. ::: ## Настройка таймеров, определяемых разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать кастомные таймеры в мобильном приложении, передайте карту `customTimers` в метод `createFlowView`. Каждый ключ карты — это идентификатор таймера, а значение — объект `DateTime`, определяющий момент окончания таймера. Пример: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView( flow: flow, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2027, 1, 1), // New Year 2027 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** таймеров, заданных разработчиком в дашборде Adapty. Словарь `customTimers` обеспечивает динамическое обновление каждого таймера с нужным значением. Например: - `CUSTOM_TIMER_NY`: время, оставшееся до конца отсчёта таймера, например до Нового года. - `CUSTOM_TIMER_6H`: время, оставшееся в 6-часовом периоде, который начался, когда пользователь открыл флоу.
После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) в новом Paywall Builder на дашборде Adapty, вы можете отобразить его в мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::warning Новый Paywall Builder работает с Flutter SDK версии 3.3.0 и выше. ::: Обратите внимание, что этот раздел посвящён пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для Remote Config пейволов в вашем мобильном приложении](fetch-paywalls-and-products-flutter). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать отображать пейволы в вашем мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-flutter) в своё мобильное приложение.
## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отображении в коде мобильного приложения. Такой пейвол содержит как то, что должно быть показано, так и то, как именно это должно быть показано. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в вашем мобильном приложении. Для обеспечения оптимальной производительности крайне важно получать пейвол и его [конфигурацию отображения](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пользователь увидит пейвол. Для получения пейвола используйте метод `getPaywall`: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы задаёте при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

| Параметры ответа: | Параметр | Описание | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) со списком идентификаторов продуктов, идентификатором пейвола, 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-flutter). ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Когда представление готово, [покажите пейвол](flutter-present-paywalls). ## Получение пейвола для аудитории по умолчанию для более быстрой загрузки \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а интернет-соединение у пользователей нестабильное, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол для аудитории по умолчанию — это обеспечит плавный пользовательский опыт вместо ситуации, когда пейвол не отображается вовсе. Чтобы решить эту задачу, вы можете использовать метод `getPaywallForDefaultAudience`, который загружает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы обратной совместимости**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть трудности. Придётся либо создавать пейволы, совместимые с текущей (устаревшей) версией, либо смириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с нерендерящимися пейволами. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, созданный для аудитории **All Users**, — то есть вы теряете персонализированный таргетинг (включая таргетинг по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getPaywall`, описанного [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Flutter SDK версии 3.2.0. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

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

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

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

| ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео на пейволе, реализуйте пользовательские ресурсы. Для изображений-заголовков и видео-заголовков есть предопределённые идентификаторы: `hero_image` и `hero_video`. В пакете пользовательских ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для других изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать превью перед запуском видео. :::important Чтобы использовать эту функцию, обновите Flutter SDK Adapty до версии 3.8.0 или выше. ::: Вот пример того, как можно передавать пользовательские ресурсы через простой словарь: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Если ресурс не найден, пейвол вернётся к внешнему виду по умолчанию. ::: ## Настройка таймеров, определяемых разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать пользовательские таймеры в мобильном приложении, передайте словарь `customTimers` в метод `createPaywallView`. Каждый ключ словаря — это идентификатор таймера, а значение — объект `DateTime`, определяющий момент окончания таймера. Пример: ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2025, 1, 1), // New Year 2025 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** пользовательских таймеров, которые вы задали в дашборде Adapty. Словарь `customTimers` гарантирует, что приложение динамически обновит каждый таймер с нужным значением. Например: - `CUSTOM_TIMER_NY`: время до окончания таймера, например до Нового года. - `CUSTOM_TIMER_6H`: оставшееся время в 6-часовом периоде, который начался, когда пользователь открыл пейвол.
--- # File: flutter-present-paywalls --- --- title: "Отображение флоу и пейволов — Flutter" description: "Отображайте флоу и пейволы в Flutter-приложениях с помощью функций монетизации Adapty." --- Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрисовать его в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол содержит и то, что должно быть показано, и то, как это должно выглядеть. :::warning Этот гайд предназначен для флоу и пейволов, созданных в Paywall Builder. Информацию о представлении **пейволов на основе Remote Config** см. в разделе [Отрисовка пейвола, созданного через Remote Config](present-remote-config-paywalls-flutter). ::: Adapty Flutter SDK предоставляет два способа отображения флоу и пейволов: - **Отдельный экран** - **Встроенный виджет** ## Отображение как отдельного экрана \{#present-as-standalone-screen\} Чтобы отобразить флоу или пейвол как отдельный экран, вызовите метод `view.present()` на объекте `view`, созданном методом [`createFlowView`](flutter-get-pb-paywalls#fetch-the-view-configuration). Каждый `view` можно показать только один раз: после закрытия он освобождается из памяти. Если нужно показать флоу или пейвол снова, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Закрытие флоу или пейвола \{#dismiss-the-flow-or-paywall\} Чтобы программно закрыть флоу или пейвол, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note После закрытия вью освобождается из памяти — повторно отобразить его невозможно. Создайте новый с помощью `createFlowView`. ::: ### Показать диалог \{#show-dialog\} Используйте этот метод вместо стандартных диалоговых окон, когда на Android отображается флоу или пейвол. На Android обычные алерты появляются позади вью, из-за чего пользователи их не видят. Этот метод гарантирует корректное отображение диалога поверх флоу или пейвола на всех платформах. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Настройка стиля представления для iOS \{#configure-ios-presentation-style\} Настройте способ отображения флоу или пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить флоу или пейвол в существующее дерево виджетов, используйте виджет `AdaptyUIFlowPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIFlowPlatformView( flow: flow, // The flow object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidReceiveError: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Чтобы платформенное представление Android работало корректно, убедитесь, что ваш `MainActivity` расширяет `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой пейвол содержит как то, что должно отображаться, так и то, как именно это должно отображаться. :::warning Этот гайд предназначен только для **пейволов на основе нового Paywall Builder**, которые требуют SDK v3.2.0 или выше. Процесс отображения пейволов различается в зависимости от версии Paywall Builder и Remote Config пейволов. - Для отображения **Remote Config пейволов** см. [Отображение пейвола, созданного с помощью Remote Config](present-remote-config-paywalls-flutter). ::: Adapty Flutter SDK предоставляет два способа отображения пейволов: - **Отдельный экран** - **Встроенный виджет** ## Отображение как отдельного экрана \{#present-as-standalone-screen\} Чтобы отобразить пейвол как отдельный экран, вызовите метод `view.present()` на объекте `view`, созданном методом [`createPaywallView`](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый `view` можно использовать только один раз. Если нужно показать пейвол повторно, снова вызовите `createPaywallView`, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Закрытие пейвола \{#dismiss-the-paywall\} Чтобы программно закрыть пейвол, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Показ диалога \{#show-dialog\} Используйте этот метод вместо стандартных диалогов на Android, когда отображается пейвол. На Android обычные алёрты появляются за пейволом и становятся невидимыми для пользователей. Этот метод обеспечивает корректное отображение диалога поверх пейвола на всех платформах. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить пейвол в существующее дерево виджетов, используйте виджет `AdaptyUIPaywallPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIPaywallPlatformView( paywall: paywall, // The paywall object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidFailRendering: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Чтобы Android platform view работал корректно, убедитесь, что ваш `MainActivity` расширяет `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: --- # File: flutter-handle-paywall-actions --- --- title: "Реакция на действия кнопок в Flutter SDK" description: "Обработка действий кнопок пейвола во Flutter с помощью Adapty для улучшения монетизации приложения." --- Если вы создаёте флоу или пейволы с помощью конструктора Adapty, важно правильно настроить кнопки: 1. Добавьте [кнопку в Builder](paywall-buttons) и назначьте ей существующее действие или создайте пользовательский идентификатор действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Закрытие экрана и открытие URL обрабатываются автоматически** стандартной реализацией `flowViewDidPerformAction`, а SDK самостоятельно обрабатывает покупки и восстановления. Все остальные действия кнопок, такие как вход в систему или открытие другого флоу, требуют реализации соответствующей логики в коде приложения. Обратите внимание, что реагирование на *завершённые* покупки и восстановления происходит в обязательных коллбэках наблюдателя — см. [Обработка событий флоу и пейвола](flutter-handling-events). ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку закрытия флоу или пейвола, в билдере добавьте кнопку и назначьте ей действие **Close**. Код писать не нужно: стандартная реализация `flowViewDidPerformAction` автоматически закрывает экран при получении `CloseAction`. :::info Системная кнопка **Back** на Android больше не закрывает экран по умолчанию. Она передаётся в `flowViewDidPerformAction` как `AndroidSystemBackAction` — обработайте её самостоятельно, если хотите, чтобы кнопка «Назад» закрывала флоу или пейвол. ::: Переопределите `flowViewDidPerformAction`, если вам нужна кастомная логика — например, чтобы закрывать экран также по системной кнопке «Назад» на Android, как в v3: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } ``` :::warning Переопределение `flowViewDidPerformAction` полностью заменяет реализацию по умолчанию — сохраните обработку `CloseAction` и `OpenUrlAction`, если хотите сохранить стандартное поведение закрытия и открытия URL. ::: ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в конструкторе и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку (например, **Terms of use** или **Privacy policy**), добавьте кнопку в конструкторе, назначьте ей действие **Open URL** и укажите нужный URL. Никакой дополнительной реализации не требуется: стандартная реализация `flowViewDidPerformAction` открывает URL нативно через `AdaptyUI().openUrl`, учитывая настройку внутреннего или внешнего браузера из дашборда. Стандартного поведения достаточно в большинстве случаев. Если вы всё же хотите открывать URL самостоятельно, переопределите обработчик: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL in whatever way fits your app break; default: break; } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в приложение: 1. В билдере добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик для действия `login`, который идентифицирует пользователя. ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и задайте ID. 2. В коде приложения реализуйте обработчик для этого ID действия. Например, если у вас есть другой набор предложений по подпискам или разовые покупки, можно добавить кнопку, которая откроет другой флоу или пейвол: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another flow or paywall break; default: break; } } ``` Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей существующее действие или создайте пользовательский ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в вашем коде. :::warning **Только покупки и восстановления обрабатываются автоматически.** Все остальные действия кнопок, например закрытие пейволов или открытие ссылок, требуют реализации соответствующей обработки в коде приложения. ::: ## Закрытие пейвола \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик для действий `CloseAction` и `AndroidSystemBackAction`. :::info В Flutter SDK действия `CloseAction` и `AndroidSystemBackAction` по умолчанию закрывают пейвол. Однако при необходимости вы можете переопределить это поведение в коде. Например, закрытие одного пейвола может запускать открытие другого. ::: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; default: break; } } ``` ## Открытие 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 в браузере. ```dart // You have to install url_launcher plugin in order to handle urls: // https://pub.dev/packages/url_launcher void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case OpenUrlAction(url: final url): final Uri uri = Uri.parse(url); launchUrl(uri, mode: LaunchMode.inAppBrowserView); break; default: break; } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку, обрабатывающую любые другие действия: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений подписок или разовых покупок, можно добавить кнопку, которая будет открывать другой пейвол: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Показать другой пейвол break; default: break; } } ``` --- # File: flutter-handling-events --- --- title: "Flutter - Обработка событий флоу и пейвола" description: "Узнайте, как обрабатывать события, связанные с подпиской, во Flutter с помощью Adapty для эффективного отслеживания взаимодействий пользователей." --- :::important Это руководство охватывает обработку событий покупок, восстановлений, выбора продукта и рендеринга. Закрытие экрана и открытие ссылок обрабатываются реализацией `flowViewDidPerformAction` по умолчанию — см. наш [гайд по обработке действий кнопок](flutter-handle-paywall-actions), чтобы переопределить их или обработать пользовательские действия кнопок. ::: Флоу и пейволы, настроенные с помощью билдера, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками, выполненных во флоу или пейволе. Ниже описано, как обрабатывать эти события. Чтобы управлять процессами, происходящими на экране флоу или пейвола в вашем мобильном приложении, или отслеживать их, реализуйте методы `AdaptyUIFlowsEventsObserver` и установите наблюдатель перед отображением любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(this); ``` Три метода наблюдателя **обязательны** — без них класс не скомпилируется: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` и `flowViewDidReceiveError`. Остальные методы опциональны. Чтобы отвязать ранее установленный наблюдатель, передайте `null` в `setFlowsEventsObserver`. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: В примерах событий ниже показаны свойства, доступные для каждого объекта, с поясняющими значениями в комментариях. ### События, генерируемые пользователем \{#user-generated-events\} #### Отображение вью \{#view-appeared\} Этот метод вызывается, когда флоу или вью пейвола появляется на экране. :::note На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. ::: ```dart showLineNumbers title="Flutter" void flowViewDidAppear(AdaptyUIFlowView view) { } ``` #### Скрытие вью \{#view-disappeared\} Этот метод вызывается, когда флоу или вью пейвола убирается с экрана. :::note На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: ```dart showLineNumbers title="Flutter" void flowViewDidDisappear(AdaptyUIFlowView view) { } ``` #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван следующий метод: ```dart showLineNumbers title="Flutter" void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ```
#### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ```
#### Завершённая покупка \{#finished-purchase\} Этот метод **обязателен**. Он вызывается при успешной покупке, отмене покупки пользователем или если покупка находится в состоянии ожидания: ```dart showLineNumbers title="Flutter" void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ```
Примеры событий (нажмите, чтобы развернуть) ```dart void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ```
:::info В отличие от v3, у этого метода нет поведения по умолчанию — экран больше не закрывается автоматически после успешной покупки. Решите самостоятельно, что происходит дальше: продолжить флоу или вызвать `view.dismiss()`. Подробнее об управлении закрытием экрана — в разделе [Реагирование на действия кнопок](flutter-handle-paywall-actions). ::: #### Завершение навигации в веб-платёжке \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```dart showLineNumbers title="Flutter" void flowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Параметры:** | Параметр | Описание | |:------------|:--------------------------------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация по веб-пейволу завершилась ошибкой; `null`, если навигация прошла успешно. | #### Неудачная покупка \{#failed-purchase\} Этот метод вызывается при неудачной попытке покупки (например, из-за проблем с оплатой или сетевых ошибок). Он **не** срабатывает при отмене пользователем или ожидающих транзакциях — они обрабатываются через `flowViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void flowViewDidFailPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` #### Восстановление начато \{#started-restore\} Если пользователь инициирует процесс восстановления покупок, этот метод будет вызван: ```dart showLineNumbers title="Flutter" void flowViewDidStartRestore(AdaptyUIFlowView view) { } ``` #### Успешное восстановление \{#successful-restore\} Этот метод **обязательный**. Если восстановление покупки прошло успешно, он будет вызван: ```dart showLineNumbers title="Flutter" void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ```
Мы рекомендуем закрывать экран, если у пользователя есть необходимый `accessLevel`. Обратитесь к разделу [Статус подписки](flutter-listen-subscription-changes), чтобы узнать, как его проверить, и к разделу [Обработка действий кнопок](flutter-handle-paywall-actions), чтобы узнать, как закрыть экран. #### Неудачное восстановление \{#failed-restore\} Если восстановление покупки завершается с ошибкой, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void flowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) { } ``` ### Загрузка данных и отображение \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте массив продуктов при инициализации, AdaptyUI самостоятельно запросит необходимые объекты с сервера. Если эта операция завершится ошибкой, AdaptyUI сообщит об этом, вызвав следующий метод: ```dart showLineNumbers title="Flutter" void flowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) { } ``` #### Ошибки отображения \{#view-errors\} Этот метод **обязателен**. Он заменяет метод `paywallViewDidFailRendering` из v3: ошибки, возникающие при рендеринге интерфейса, а также другие ошибки представления, передаются через него. После реализации метода решение о закрытии остаётся за вами — мы рекомендуем закрывать представление при таких ошибках; именно так ведёт себя встроенная логика SDK по умолчанию, когда наблюдатель не задан: ```dart showLineNumbers title="Flutter" void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { // log the error and dismiss the broken view view.dismiss(); } ``` В нормальной ситуации ошибки рендеринга возникать не должны, поэтому если вы с ними столкнётесь — пожалуйста, сообщите нам. ### Аналитические события \{#analytics-events\} Опциональный метод `flowViewDidReceiveAnalyticEvent` предназначен для получения пользовательских аналитических событий из флоу. Флоу пока не отправляет такие события в ваш код, поэтому реализовывать этот метод не нужно. ### Обработка покупок в режиме наблюдателя \{#handle-purchases-in-observer-mode\} Если вы активировали SDK в [режиме наблюдателя](implement-observer-mode-flutter) и отображаете флоу или пейвол, отрисованный Adapty, SDK не совершает покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления, SDK вызывает ваш `AdaptyUIObserverModeResolver`. Подробнее о настройке читайте в разделе [Отображение флоу в режиме наблюдателя](flutter-present-flows-in-observer-mode). ### Обработка системных запросов \{#handle-system-requests\} `AdaptyUISystemRequestsHandler` (регистрируется через `AdaptyUI().setSystemRequestsHandler(...)`) предназначен для системных запросов из флоу: запросы разрешений ОС (например, push-уведомления или доступ к камере) и запросы на оценку в App Store. Флоу пока не инициируют такие запросы, поэтому регистрировать обработчик не нужно. Если вы регистрируете обработчик, обратите внимание: `handlePermission` — это обязательный метод класса; запросите разрешение своим кодом, затем верните `AdaptyUIPermissionResult.granted()` или `AdaptyUIPermissionResult.denied()`; `handleAppReviewRequest` — необязательный.
:::important Этот гайд охватывает обработку событий покупок, восстановления, выбора продуктов и отображения пейвола. Также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее см. в нашем [гайде по обработке действий кнопок](flutter-handle-paywall-actions). ::: Пейволы, созданные в [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди них — нажатия кнопок (кнопки закрытия, URL, выбор продукта и т. д.), а также уведомления о действиях, связанных с покупками, выполненных на пейволе. Ниже описано, как обрабатывать эти события. :::warning Это руководство предназначено **только для пейволов, созданных в новом Paywall Builder**, для работы с которыми требуется Adapty SDK v3.0 или более поздней версии. ::: Чтобы контролировать или отслеживать процессы, происходящие на экране пейвола в вашем мобильном приложении, реализуйте методы `AdaptyUIPaywallsEventsObserver` и установите наблюдатель до отображения любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setPaywallsEventsObserver(this); ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: В примерах событий ниже показаны свойства, доступные для каждого объекта, с иллюстративными значениями в комментариях. ### События, генерируемые пользователем \{#user-generated-events\} #### Пейвол появился \{#paywall-appeared\} Этот метод вызывается, когда представление пейвола отображается на экране. :::note На iOS также вызывается, когда пользователь нажимает на [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола, и веб-пейвол открывается во встроенном браузере. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Пейвол исчез \{#paywall-disappeared\} Этот метод вызывается, когда представление пейвола убирается с экрана. :::note На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), этот метод будет вызван: ```dart showLineNumbers title="Flutter" void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ```
#### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ```
#### Завершённая покупка \{#finished-purchase\} Этот метод вызывается, когда покупка завершается успешно, пользователь отменяет покупку или покупка оказывается в ожидании: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ```
Примеры событий (нажмите, чтобы развернуть) ```dart void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ```
Мы рекомендуем закрывать экран в этом случае. Подробнее о закрытии экрана пейвола см. в разделе [Реакция на действия кнопок](flutter-handle-paywall-actions). #### Завершение навигации веб-платежа \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это включает как успешные, так и неудачные попытки навигации: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Параметры:** | Параметр | Описание | |:------------|:-----------------------------------------------------------------------------------------------------------------| | **product** | `AdaptyPaywallProduct` — продукт, для которого открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация по веб-пейволу завершилась с ошибкой; `null`, если навигация прошла успешно. |
Примеры событий (нажмите, чтобы развернуть) ```dart void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { // product — AdaptyPaywallProduct?: product?.vendorProductId; // 'premium_monthly' if (error == null) { // navigation succeeded } else { // error — AdaptyError: error.code; // AdaptyErrorCode.networkFailed (2005) error.message; // 'Network request failed' error.detail; // platform-specific underlying error, or null } } ```
#### Неудачная покупка \{#failed-purchase\} Этот метод вызывается, когда покупка завершается ошибкой (например, из-за проблем с оплатой или сетевых ошибок). Он **не** срабатывает при отмене пользователем или незавершённых транзакциях — они обрабатываются через `paywallViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' // error — AdaptyError: error.code; // AdaptyErrorCode.productPurchaseFailed (1006) error.message; // 'Product purchase failed.' error.detail; // platform-specific underlying error, or null } ```
#### Восстановление начато \{#started-restore\} Когда пользователь инициирует процесс восстановления покупок, вызывается этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Успешное восстановление \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ```
Мы рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Обратитесь к разделу [Статус подписки](flutter-listen-subscription-changes), чтобы узнать, как его проверить, и к разделу [Реагирование на действия кнопок](flutter-handle-paywall-actions), чтобы узнать, как закрыть экран пейвола. #### Ошибка восстановления \{#failed-restore\} Если восстановление покупки завершится с ошибкой, будет вызван следующий метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.receiveRestoredTransactionsFailed (1011) error.message; // 'Error occurred in the process of restoring purchases.' error.detail; // platform-specific underlying error, or null } ```
### Получение данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте массив продуктов при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится ошибкой, AdaptyUI сообщит о ней, вызвав следующий метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.productRequestFailed (1002) error.message; // 'Unable to fetch available In-App Purchase products at the moment.' error.detail; // platform-specific underlying error, or null } ```
#### Ошибки рендеринга \{#rendering-errors\} Если во время отображения интерфейса возникает ошибка, она сообщается путём вызова этого метода. По умолчанию (начиная с v3.15.2) пейвол автоматически закрывается при ошибке рендеринга, но при необходимости это поведение можно переопределить. ```dart showLineNumbers title="Flutter" void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // Default behavior: view.dismiss() // Override with custom logic if needed, for example: // - Log the error // - Show an error message to the user } ```
Пример события (нажмите, чтобы развернуть) ```dart void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.jsException (4105) error.message; // 'An exception was thrown from JS during AdaptyUI flow execution.' error.detail; // platform-specific underlying error, or null // Default behavior: view.dismiss() } ```
В нормальной ситуации такие ошибки возникать не должны, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам.
--- # File: flutter-use-fallback-paywalls --- --- title: "Flutter - Использование резервных пейволов" description: "Обработка случаев, когда пользователи офлайн или серверы Adapty недоступны" --- :::warning Резервные пейволы поддерживаются Flutter SDK v2.11 и более поздними версиями. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} 1. Добавьте файлы резервной конфигурации в директорию `assets` приложения в корне проекта. 2. Вызовите метод `.setFallback` **до** того, как запросите целевой пейвол или онбординг. ```dart showLineNumbers title="Flutter" final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { await Adapty().setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры: | Parameter | Description | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **assetId** | Путь к файлу резервной конфигурации. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: flutter-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали во Flutter SDK" description: "Управляйте локализациями приложения и кодами локали для охвата глобальной аудитории." --- ## Почему это важно \{#why-this-is-important\} Коды локалей используются, когда Adapty выбирает локализацию для флоу и когда вы читаете Remote Config для кастомного пейвола. Коды локалей — штука непростая: они отличаются от платформы к платформе, поэтому Adapty использует единый внутренний стандарт для всех поддерживаемых платформ. Понимание этого стандарта поможет вам предсказать, какую локализацию получит пользователь. ## Стандарт кодов локали в 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 ищет локализацию, соответствующую языковому стандарту пользователя, происходит следующее: 1. Строка локали преобразуется в нижний регистр, все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Adapty ищет локализацию с полностью совпадающим кодом локали 3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (`pt` для `pt-br`) и ищет соответствующую локализацию 4. Если совпадение снова не найдено, Adapty возвращает локализацию по умолчанию — `en` Таким образом, `'pt_BR'`, `pt-BR` и `pt-br` — все они указывают на одну и ту же локализацию. ## Реализация локализаций \{#implementing-localizations\} В SDK v4 передавать код локали при получении флоу не нужно. - **Пейволы из Flow Builder и Paywall Builder**: Adapty автоматически определяет локализацию на основе настроек устройства и локализаций, заданных в билдере. Отображайте флоу через `createFlowView` — код локали не требуется. - **Кастомные пейволы (Remote Config)**: `getFlow` возвращает все настроенные локализации в `flow.remoteConfigs`. Каждая запись содержит код локали `locale` и содержимое конфига (строка `data` или разобранный `dictionary`). Выберите нужную запись по настройкам пользователя, задав собственный фолбэк: ```dart showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; // the first remote config, if present // read your values from config?.dictionary ``` Правила сопоставления кодов локали, описанные выше, объясняют, как Adapty нормализует коды `locale`, хранящиеся в каждом Remote Config. ## Почему это важно \{#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 извлекайте значение по этому ключу, как показано ниже: ```dart showLineNumbers // 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files /* app_en.arb */ "adapty_paywalls_locale": "en", /* app_es.arb */ "adapty_paywalls_locale": "es", /* app_pt_br.arb */ "adapty_paywalls_locale": "pt-br", // 2. Extract and use the locale code final locale = AppLocalizations.of(context)!.adapty_paywalls_locale; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Таким образом вы полностью контролируете, какая локализация будет загружена для каждого пользователя вашего приложения. ## Реализация локализаций: альтернативный способ \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться без явного указания кодов локали для каждой локализации. Это означает извлечение кода локали из других объектов, предоставляемых вашей платформой, например: ```dart showLineNumbers final locale = Localizations.localeOf(context).languageCode; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Обратите внимание, что мы не рекомендуем этот подход по нескольким причинам: 1. На iOS предпочтительные языки и текущая локаль — не одно и то же. Чтобы локализация подбиралась корректно, придётся либо опираться на логику Apple (которая работает из коробки при рекомендованном подходе с локализованными строковыми файлами), либо реализовывать её самостоятельно. 2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS устройство может вернуть локаль вида `ar_OM@numbers='latn'`, которая будет отправлена на сервер. В ответ вы получите не локализацию `ar-om`, которую ожидали, а `ar` — что, скорее всего, не то, что нужно. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. --- # File: flutter-web-paywall --- --- title: "Реализация веб-пейволов в Flutter SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок App Store." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.6.1 или выше. ::: Если вы работаете с пейволом собственной разработки, для обработки веб-пейволов нужно использовать метод SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, который позволяет Adapty связать конкретный пейвол, показанный определённому пользователю, с веб-страницей, на которую он перенаправляется. 2. Отслеживает возвращение пользователя в приложение и затем с короткими интервалами вызывает `.getProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если оплата прошла успешно и права доступа обновились, подписка активируется в приложении практически мгновенно. ```dart showLineNumbers title="Flutter" try { await Adapty().openWebPaywall(product: ); // The web paywall will be opened } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` :::note Метод `openWebPaywall` существует в двух вариантах: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет данные продукта в URL. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных продукта в URL. Используйте его, если продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. В SDK v4 параметр `paywall` принимает `AdaptyFlowPaywall` — вариацию пейвола из полученного флоу. Убедитесь, что `flow.paywalls` не пустой, прежде чем обращаться к его элементам, например `flow.paywalls[0]`. ::: #### Обработка ошибок \{#handle-errors\} | Ошибка | Описание | Рекомендуемое действие | |-----------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | У пейвола не настроен URL для веб-покупки | Проверьте, правильно ли настроен пейвол в дашборде Adapty | | AdaptyError.productWithoutPurchaseUrl | У продукта отсутствует URL для веб-покупки | Проверьте настройки продукта в дашборде Adapty | | AdaptyError.failedOpeningWebPaywallUrl | Не удалось открыть URL в браузере | Проверьте настройки устройства или предложите альтернативный способ оплаты | | AdaptyError.failedDecodingWebPaywallUrl | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL корректны и имеют правильный формат | ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, можно открывать веб-пейволы во встроенном браузере. Это позволяет отображать страницу веб-покупки прямо внутри приложения, и пользователи смогут завершить транзакцию, не переключаясь между приложениями. Чтобы включить это, задайте для параметра `in` значение `.inAppBrowser`: ```dart showLineNumbers try { await Adapty().openWebPaywall( product: , openIn: AdaptyWebPresentation.inAppBrowser, ); // The web paywall will be opened in the in-app browser } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` --- # File: flutter-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder во Flutter SDK" description: "Устранение неполадок Paywall Builder во Flutter SDK" --- Этот гайд поможет вам устранить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, во Flutter SDK. ## Получение конфигурации пейвола завершается ошибкой \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `createPaywallView` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. ## Число просмотров пейвола слишком велико \{#the-paywall-view-number-is-too-big\} **Проблема**: Счётчик просмотров пейвола показывает число вдвое больше ожидаемого. **Причина**: Возможно, вы вызываете `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` в своём коде, что дублирует счётчик просмотров, если вы используете Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать этот метод не нужно. **Решение**: Убедитесь, что вы не вызываете `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` в своём коде, если используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: У вас возникают другие проблемы, связанные с Paywall Builder, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](flutter-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: flutter-present-flows-in-observer-mode --- --- title: "Показ флоу в режиме Observer в Flutter SDK" description: "Показывайте флоу и пейволы Paywall Builder в режиме Observer в вашем Flutter-приложении, обрабатывая покупки собственным кодом." --- Если вы настроили флоу или пейвол с помощью билдера, вам не нужно беспокоиться об их рендеринге в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол уже содержит и то, что нужно показать, и то, как именно это должно выглядеть. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [Отображение флоу и пейволов](flutter-present-paywalls). ::: :::info Эта функция доступна начиная с Adapty Flutter SDK 4.0 — ранее она была доступна только в нативных SDK для iOS и Android. Ознакомьтесь с [руководством по миграции](migration-to-flutter-sdk-v4), чтобы выполнить обновление. :::
Прежде чем начать показывать флоу (нажмите, чтобы развернуть) 1. Настройте начальную интеграцию Adapty [с App Store](initial_ios) и [с Google Play](initial-android). 2. Установите и настройте Adapty SDK. Обязательно задайте параметр `observerMode` равным `true`. Обратитесь к [гайду по установке Flutter SDK](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement). 6. [Получите флоу и их конфигурацию](flutter-get-pb-paywalls) в коде мобильного приложения.
В режиме Observer SDK не выполняет покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления во флоу или пейволе, отрисованном Adapty, SDK вызывает ваш `AdaptyUIObserverModeResolver` — выполните покупку или восстановление там с помощью собственного кода. 1. Реализуйте `AdaptyUIObserverModeResolver`: ```dart showLineNumbers title="Flutter" class MyObserverModeResolver extends AdaptyUIObserverModeResolver { @override void observerModeDidInitiatePurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, void Function() onStartPurchase, void Function() onFinishPurchase, ) { onStartPurchase(); // the view shows its loading indicator // make the purchase with your own code, then: onFinishPurchase(); // the view hides the loading indicator } @override void observerModeDidInitiateRestore( AdaptyUIFlowView view, void Function() onStartRestore, void Function() onFinishRestore, ) { onStartRestore(); // restore purchases with your own code, then: onFinishRestore(); } } ``` Метод `observerModeDidInitiatePurchase` сообщает вам о том, что пользователь инициировал покупку, а `observerModeDidInitiateRestore` — что пользователь инициировал восстановление. В ответ на эти события запустите своё кастомное флоу покупки или восстановления. Также не забудьте вызвать следующие коллбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректного поведения флоу — например, для отображения лоадера: | Callback | Description | | :----------------- | :----------------------------------------------------------------------------------------------- | | onStartPurchase() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о начале восстановления. | | onFinishRestore() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о завершении восстановления. | 2. Зарегистрируйте резолвер до отображения любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setObserverModeResolver(MyObserverModeResolver()); ``` 3. Создайте и отобразите флоу как обычно: [получите флоу и создайте его представление](flutter-get-pb-paywalls), затем [отобразите его](flutter-present-paywalls). Никаких дополнительных параметров не требуется — после регистрации резолвера все покупки и восстановления в пейволах и флоу Adapty будут проходить через него. :::warning Не забудьте [сообщить о транзакции и связать её с пейволом](report-transactions-observer-mode-flutter). В противном случае Adapty не распознает транзакцию и не определит, с какого пейвола была совершена покупка. ::: --- # File: flutter-quickstart-manual --- --- title: "Подключение покупок в кастомном пейволе во Flutter SDK" description: "Интегрируйте Adapty SDK в ваши кастомные пейволы Flutter для включения встроенных покупок." --- Это руководство описывает, как интегрировать Adapty в кастомные пейволы. Сохраняйте полный контроль над реализацией пейвола, пока SDK Adapty получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. Руководство использует API Adapty Flutter SDK v4 — если вы используете v3, см. [гайд по миграции](migration-to-flutter-sdk-v4) с соответствующими именами методов. :::important **Это руководство предназначено для разработчиков, реализующих кастомные пейволы.** Если вы хотите подключить покупки максимально просто, используйте [Adapty Paywall Builder](flutter-quickstart-paywalls). С Paywall Builder вы создаёте пейволы в визуальном редакторе без кода, Adapty берёт на себя всю логику покупок, а тестировать разные дизайны можно без переpublikации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет изменять продукты, цены и офферы без обновления кода приложения. В SDK v4 варианты пейвола для плейсмента передаются через объект **flow** — вы получаете флоу и запрашиваете из него продукты. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, нужно создать **пейвол** и добавить его в **плейсмент**. Такая настройка позволяет получать ваши продукты. Чтобы разобраться, что нужно сделать в дашборде, воспользуйтесь гайдом по быстрому старту [здесь](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с серверной аутентификацией, так и без неё. При этом Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по быстрому старту с идентификацией](flutter-quickstart-identify), чтобы разобраться в деталях и убедиться, что вы правильно работаете с пользователями. ## Шаг 1. Получение продуктов \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```dart showLineNumbers Future loadPaywall() async { try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final products = await Adapty().getPaywallProducts(flow: flow); // Use products to build your custom paywall UI } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 2. Принятие покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит флоу покупки и вернёт обновлённый профиль. ```dart showLineNumbers Future purchaseProduct(AdaptyPaywallProduct product) async { try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // Purchase successful, profile updated break; case AdaptyPurchaseResultUserCancelled(): // User canceled the purchase break; case AdaptyPurchaseResultPending(): // Purchase is pending (e.g., user will pay offline with cash) break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```dart showLineNumbers Future restorePurchases() async { try { final profile = await Adapty().restorePurchases(); // Restore successful, profile updated } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 4. Проверьте статус подписки \{#step-4-check-the-subscription-status\} После покупки или восстановления проверьте [уровень доступа](access-level) пользователя, чтобы решить, показывать пейвол или открывать платные функции. Методы `makePurchase` и `restorePurchases` уже возвращают обновлённый профиль; когда нужно получить текущий статус в другом месте приложения, используйте метод `getProfile`: ```dart showLineNumbers Future hasPremiumAccess() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['premium']?.isActive ?? false; } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } return false; } ``` Другие способы проверки и отслеживания статуса подписки, включая получение обновлений в реальном времени, описаны в разделе [Проверка статуса подписки](flutter-check-subscription-status). ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что вы можете совершить тестовую покупку через пейвол. Чтобы увидеть, как это работает в production-реализации, ознакомьтесь с [PurchasesObserver](https://github.com/adaptyteam/AdaptySDK-Flutter/blob/master/example/lib/purchase_observer.dart) в нашем примере приложения — там показана обработка покупок с корректной обработкой ошибок, наблюдателями UI и полноценной интеграцией SDK. --- # File: fetch-paywalls-and-products-flutter --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Flutter SDK" description: "Получайте пейволы и продукты в Adapty Flutter SDK для улучшения монетизации пользователей." --- Прежде чем отображать Remote Config и кастомные пейволы, необходимо получить информацию о них. Обратите внимание: этот раздел посвящён Remote Config и кастомным пейволам. Если вам нужна информация о получении флоу и пейволов, созданных в Paywall Builder, обратитесь к разделу [Получение флоу и пейволов](flutter-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-flutter) в своё мобильное приложение.
## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты интегрируются в пейволы, позволяя показывать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не вшивайте ID продуктов в код.** Единственный ID, который нужно хардкодить, — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — показывайте все без изменений в коде. ::: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

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

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

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

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

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

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

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

| :::note В v4 метод `getFlow` не принимает параметр `locale`. Для кастомных пейволов все доступные локализации возвращаются в Remote Config флоу (`flow.remoteConfigs`) — выберите ту, которая соответствует языку устройства или настройкам приложения. См. [Локализации и коды локалей](flutter-localizations-and-locale-codes). ::: Параметры ответа: | Parameter | Description | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`instanceIdentity`, `variationId`), именем, плейсментом, вариантами пейволов (`paywalls`) и Remote Config-ами (`remoteConfigs`). | ## Получение продуктов \{#fetch-products\} Получив флоу, вы можете запросить массив продуктов, соответствующих ему: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(flow: flow); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобится доступ к свойствам объекта [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание, что локализация основана на стране стора, выбранной пользователем, а не на локали самого устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.price.localizedString`. Эта локализация основана на данных локали устройства. Цену в числовом виде можно получить через `product.price.amount`. Значение будет указано в местной валюте. Чтобы получить соответствующий символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т.д.), используйте `product.subscription?.localizedPeriod`. Эта локализация основана на локали устройства. Чтобы получить период подписки программно, используйте `product.subscription?.period`. Оттуда можно обратиться к перечислению `unit`, чтобы получить длину периода (day, week, month, year или unknown). Значение `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в свойстве unit будет `AdaptyPeriodUnit.month`, а в numberOfUnits — `3`. | | **Introductory Offer** | Чтобы отобразить значок или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:
• `paymentMode`: перечисление со значениями `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` и `AdaptyPaymentMode.unknown`. Бесплатные пробные периоды имеют тип `AdaptyPaymentMode.freeTrial`.
• `price`: цена со скидкой в числовом виде. Для бесплатных пробных периодов здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативно можно получить отдельные детали периода предложения с помощью этого свойства. Оно работает так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: форматированный период подписки для скидки с учётом локали пользователя. | ## Ускорьте загрузку флоу с помощью флоу аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают с медленным интернетом, загрузка флоу может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать флоу по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, можно воспользоваться методом `getFlowForDefaultAudience`, который получает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу с помощью метода `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-flutter#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки флоу, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getFlow`, описанного [выше](fetch-paywalls-and-products-flutter#fetch-flow-information). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

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

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

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

|
Прежде чем отображать Remote Config и кастомные пейволы, необходимо получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Если вам нужны инструкции по получению пейволов, созданных с помощью Paywall Builder, обратитесь к статье [Получение пейволов Paywall Builder и их конфигурации](flutter-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-flutter) в своём мобильном приложении.
## Получение данных о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты добавляются в пейволы, позволяя отображать их в нужных плейсментах мобильного приложения. Чтобы показать продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не указывайте ID продуктов в коде явно.** Единственный ID, который нужно прописать в коде — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, отображайте все без изменений в коде. ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

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

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

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

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

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

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

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

| Не хардкодьте идентификаторы продуктов! Поскольку пейволы настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код корректно обрабатывает подобные сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно захардкодить, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, соответствующих ему: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) со следующими свойствами: идентификатор продукта, название продукта, цена, валюта, длительность подписки и ряд других параметров. | При реализации собственного дизайна пейвола вам, скорее всего, потребуется доступ к свойствам объекта [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на выбранной пользователем стране в сторе, а не на локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену как число через `product.price.amount` — значение будет в местной валюте. Чтобы получить символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделю, месяц, год и т. д.), используйте `product.subscription?.localizedPeriod`. Локализация основана на локали устройства. Чтобы получить период подписки программно, используйте `product.subscription?.period`. Оттуда можно обратиться к enum `unit`, чтобы получить единицу длительности (день, неделя, месяц, год или unknown). Значение `numberOfUnits` вернёт количество единиц периода. Например, для квартальной подписки в свойстве unit будет `AdaptyPeriodUnit.month`, а в numberOfUnits — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:
• `paymentMode`: enum со значениями `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` и `AdaptyPaymentMode.unknown`. Бесплатные пробные периоды имеют тип `AdaptyPaymentMode.freeTrial`.
• `price`: скидочная цена в виде числа. Для бесплатных пробных периодов здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства и описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативно можно получить отдельные детали периода предложения через это свойство — оно работает так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: форматированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи находятся в зоне слабого интернета, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать пейвол по умолчанию, чтобы пользователь не остался без пейвола вовсе. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-flutter#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: Если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на этой версии могут столкнуться с нерендерящимися пейволами. - **Потеря таргетинга**: Все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-flutter#fetch-paywall-information). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с версии Flutter SDK 3.2.0. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

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

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

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

|
--- # File: present-remote-config-paywalls-flutter --- --- title: "Отображение пейвола на основе Remote Config в Flutter SDK" description: "Узнайте, как отображать пейволы с Remote Config в Adapty Flutter SDK для персонализации пользовательского опыта." --- Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config гибко адаптируется под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config, а дальше вы сами управляете отображением пейвола. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} В v4 флоу содержит список `remoteConfigs` — по одному Remote Config на каждую настроенную локализацию. Выберите запись, соответствующую локали пользователя, и извлеките нужные значения. Подробнее о выборе подходящей локализации — в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes). ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // one entry per configured localization; fall back to the first one final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; final String? headerText = config?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован для различных размеров экранов и ориентаций мобильных телефонов, обеспечивая удобный и бесперебойный пользовательский опыт на разных устройствах. :::warning Обязательно фиксируйте событие просмотра пейвола, как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После отображения пейвола переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](flutter-making-purchases). Рекомендуем [создать резервный пейвол](flutter-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но события просмотра пейвола нужно логировать вручную — только вы знаете, когда пользователь видит пейвол. Чтобы залогировать событие просмотра пейвола, вызовите `.logShowFlow(flow: flow)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow: flow)` не нужно, если вы отображаете флоу или пейволы, созданные в [конструкторе](adapty-paywall-builder). ::: ```dart showLineNumbers try { await Adapty().logShowFlow(flow: flow); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:----------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`. | Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы полностью контролируете содержимое и внешний вид пейвола. Мы предоставляем метод для получения Remote Config, чтобы вы могли самостоятельно отобразить пейвол, настроенный через него. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` На этом этапе, получив все необходимые значения, можно переходить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под различные экраны и ориентации мобильных телефонов, обеспечивая удобный пользовательский опыт на всех устройствах. :::warning Обязательно зафиксируйте событие просмотра пейвола, как описано ниже, — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](flutter-making-purchases). Рекомендуем [создать резервный пейвол](flutter-use-fallback-paywalls). Он будет показываться пользователю при отсутствии интернета или кэша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но логирование просмотров пейвола требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы залогировать событие просмотра пейвола, вызовите `.logShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowPaywall(paywall)` не нужно, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | --- # File: flutter-making-purchases --- --- title: "Совершение покупок в мобильном приложении с Flutter 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)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужны пошаговые инструкции?** Ознакомьтесь с [гайдом по быстрому старту](flutter-implement-paywalls-manually) — там есть полное руководство по реализации с подробным контекстом. ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | обязательный | Объект [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

Если запрос выполнен успешно, ответ содержит этот объект. Объект [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках в приложении.

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

| :::warning **Примечание:** если вы используете Apple StoreKit версии ниже v2.0 и Adapty SDK версии ниже v2.9.0, вам нужно указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Этот метод в настоящее время устарел и не рекомендуется Apple. ::: ## Смена подписки при совершении покупки \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора: - В App Store подписка обновляется автоматически в рамках группы подписок. Если пользователь покупает подписку из одной группы, уже имея активную из другой, обе подписки будут активны одновременно. - В Google Play подписка не обновляется автоматически. Переключение нужно реализовать в коде вашего приложения, как описано ниже. Чтобы заменить подписку на другую в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```dart showLineNumbers try { final subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( 'OLD_PRODUCT_ID', AdaptyAndroidSubscriptionUpdateReplacementMode.immediateWithTimeProration, ); final result = await Adapty().makePurchase( product: product, parameters: AdaptyPurchaseParameters( subscriptionUpdateParams: subscriptionUpdateParams, ), ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- |:--------------------------------------------------------------------------------------------------------| | **parameters** | обязательный | объект `AdaptyPurchaseParameters` с полем `subscriptionUpdateParams`, установленным в объект [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.html). | Подробнее о подписках и режимах замены можно прочитать в документации 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()). Примечание: реальное изменение подписки произойдёт только по окончании текущего расчётного периода. ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\}
Об офферных кодах Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки.
Чтобы показать экран активации кода в приложении: ```dart showLineNumbers try { await Adapty().presentCodeRedemptionSheet(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` :::danger По нашим наблюдениям, экран активации промокода в некоторых приложениях работает ненадёжно. Рекомендуем перенаправлять пользователя напрямую в App Store. Для этого нужно открыть URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ### Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для таких планов. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleEnablePendingPrepaidPlans(true), ); ``` --- # File: flutter-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с Flutter SDK" description: "Узнайте, как восстановить покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок в iOS и Android позволяет пользователям восстановить доступ к ранее купленному контенту — подпискам или встроенным покупкам — без повторного списания средств. Это особенно удобно для тех, кто переустановил приложение или перешёл на новое устройство и хочет снова получить доступ к оплаченному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — дополнительный код писать не нужно. Если это ваш случай — этот шаг можно пропустить. ::: Чтобы восстановить покупку, если вы не используете [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: ```dart showLineNumbers try { final profile = await Adapty().restorePurchases(); if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) { // successful access restore } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | |---------|-----------| | **Profile** |

Объект [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках.

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

| :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-flutter --- --- title: "Реализация Observer mode во Flutter SDK" description: "Реализуйте Observer mode в Adapty для отслеживания событий подписки пользователей во Flutter SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, можно воспользоваться [Observer mode](observer-vs-full-mode). В базовом варианте Observer Mode предоставляет расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это подходит вам, нужно лишь: 1. Включить этот режим при настройке SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 2. [Передавать транзакции](report-transactions-observer-mode-flutter) из вашей существующей инфраструктуры покупок в Adapty. ## Настройка режима Observer \{#observer-mode-setup\} Включите режим Observer, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме Observer SDK Adapty не закрывает транзакции самостоятельно — позаботьтесь об этом в своём коде. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, которое управляет [Observer mode](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы и A/B-тесты Adapty, это возможно — но в режиме Observer Mode потребуется дополнительная настройка. Вот что нужно сделать помимо шагов выше: 1. Отображайте пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-flutter). 3. [Свяжите пейволы](report-transactions-observer-mode-flutter) с транзакциями покупок. :::tip В SDK v4 вы также можете отображать флоу и пейволы, отрисованные Adapty, в режиме Observer: зарегистрируйте `AdaptyUIObserverModeResolver`, чтобы выполнять покупку или восстановление с помощью вашего собственного кода, когда пользователь нажимает соответствующую кнопку. См. [Отображение флоу в режиме Observer](flutter-present-flows-in-observer-mode). ::: --- # File: report-transactions-observer-mode-flutter --- --- title: "Отчёт о транзакциях в Observer Mode в Flutter SDK" description: "Отправляйте информацию о транзакциях покупок в Adapty Observer Mode для аналитики пользователей и отслеживания дохода во Flutter SDK." --- В режиме Observer Adapty SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно передавать транзакции из стора вручную. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать Adapty о каждой транзакции. :::warning **Не пропускайте отчёт о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит корректную аналитику пейволов. ```dart showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | обязательный |
  • Для iOS: идентификатор транзакции.
  • Для Android: строковый идентификатор `purchase.getOrderId` покупки, где покупка — это экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки биллинга.
| | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). |
В режиме Observer, SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему покупок. Вам нужно передавать транзакции из вашего стора или восстанавливать их. Важно настроить это **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction` на обеих платформах для явной передачи каждой транзакции, а также `restorePurchases` на Android как дополнительный шаг, чтобы Adapty её распознала. :::warning **Не пропускайте отчётность о транзакции и восстановление покупки!** Если не вызвать эти методы, Adapty не распознает транзакцию — она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейвола. ```dart showLineNumbers // every time when calling transaction.finish() if (Platform.isAndroid) { try { await Adapty().restorePurchases(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | обязательный |
  • Для 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 — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга).
| | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). |
**Передача транзакций** - Версии до 3.1.x автоматически отслеживают транзакции в App Store, поэтому передавать их вручную не нужно. - Версия 3.2 не поддерживает Observer Mode. **Передача транзакций** Используйте `restorePurchases`, чтобы передать транзакцию в Adapty в режиме Observer Mode, как описано на странице [Восстановление покупок в коде приложения](flutter-restore-purchase). :::warning **Не пропускайте передачу транзакций!** Если вы не вызовете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: **Привязка пейволов к транзакциям** SDK Adapty не может самостоятельно определить источник покупок, поскольку вы сами их обрабатываете. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в режиме Observer, вам необходимо в коде мобильного приложения связать транзакцию из стора с соответствующим пейволом. Это важно сделать правильно до релиза приложения, иначе это приведёт к ошибкам в аналитике. ```dart final transactionId = transaction.transactionIdentifier final variationId = paywall.variationId try { await Adapty().setVariationId('transactionId', variationId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ```
--- # File: flutter-troubleshoot-purchases --- --- title: "Устранение неполадок с покупками в Flutter SDK" description: "Устранение неполадок с покупками в Flutter SDK" --- Этот гайд поможет вам решить распространённые проблемы при реализации покупок вручную в Flutter 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 в режиме наблюдателя \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: При использовании `makePurchase` в режиме наблюдателя возникает ошибка `AdaptyError.cantMakePayments`. **Причина**: В режиме наблюдателя покупки нужно обрабатывать на вашей стороне, а не использовать метод `makePurchase` из Adapty. **Решение**: Если вы используете `makePurchase` для покупок, отключите режим наблюдателя. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме наблюдателя. Подробнее см. в разделе [Реализация режима наблюдателя](implement-observer-mode-flutter). ## Ошибка 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, означающая, что биллинг недоступен на устройстве. **Решение**: Эта ошибка не связана с 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 до последней версии с помощью [гайдов по миграции](flutter-sdk-migration-guides). Многие проблемы устранены в новых версиях SDK. --- # File: flutter-identifying-users --- --- title: "Идентификация пользователей в Flutter SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта подписок." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. Пользователей можно искать по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Передача пользовательского идентификатора при инициализации \{#setting-customer-user-id-on-configuration\} Если у вас есть идентификатор пользователя на момент инициализации, просто передайте его в качестве параметра `customerUserId` в метод `.activate()`: ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) ); } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Установка идентификатора пользователя после инициализации \{#setting-customer-user-id-after-configuration\} Если при инициализации SDK у вас не было идентификатора пользователя, его можно задать в любой момент позже с помощью метода `.identify()`. Чаще всего этот метод используют после регистрации или авторизации — когда анонимный пользователь становится аутентифицированным. ```dart showLineNumbers try { await Adapty().identify(customerUserId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь снова входит в свой аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких сценариях SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — эти данные необходимо отправить повторно для идентифицированного пользователя. Также важно помнить, что после идентификации пользователя нужно заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ### Выход и вход \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта пользователя в любое время, вызвав метод `.logout()`: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` После этого можно авторизовать пользователя с помощью метода `.identify()`. ## Назначение `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связывать транзакции App Store с вашим внутренним идентификатором пользователя. StoreKit привязывает этот токен к каждой транзакции, поэтому ваш бэкенд может сопоставлять данные App Store с конкретными пользователями. Используйте стабильный UUID, генерируемый один раз для каждого пользователя, и применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут корректно привязаны к нужному пользователю. Токен можно задать двумя способами — при активации SDK или при идентификации пользователя. :::important Всегда передавайте `appAccountToken` вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: ```dart showLineNumbers // Во время конфигурации: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN") ); } catch (e) { // обработайте ошибку } // Или при идентификации пользователей try { await Adapty().identify(customerUserId, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN"); } on AdaptyError catch (adaptyError) { // обработайте ошибку } catch (e) { } ``` ### Установка обфусцированных идентификаторов аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта для определённых сценариев использования — с целью защиты конфиденциальности и безопасности пользователей. Эти идентификаторы позволяют Google Play отслеживать покупки, сохраняя анонимность пользователей, что особенно важно для предотвращения мошенничества и аналитики. Задавать эти идентификаторы может потребоваться, если приложение работает с чувствительными пользовательскими данными или если вы обязаны соблюдать определённые требования по защите персональных данных. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские данные. ```dart showLineNumbers // Во время настройки: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID") ); } catch (e) { // обработка ошибки } // Или при идентификации пользователей try { await Adapty().identify(customerUserId, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID"); } on AdaptyError catch (adaptyError) { // обработка ошибки } catch (e) { } ``` ## Определение пользователей на разных устройствах \{#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: flutter-setting-user-attributes --- --- title: "Задание атрибутов пользователя в Flutter SDK" description: "Узнайте, как задавать атрибуты пользователя в Adapty для более точной сегментации аудитории." --- Вы можете задавать пользователям вашего приложения дополнительные атрибуты: email, номер телефона и другие. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Настройка атрибутов пользователя \{#setting-user-attributes\} Чтобы задать атрибуты пользователя, вызовите метод `.updateProfile()`: ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setEmail("email@email.com") ..setPhoneNumber("+18888888888") ..setFirstName('John') ..setLastName('Appleseed') ..setGender(AdaptyProfileGender.other) ..setBirthday(DateTime(1970, 1, 3)); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Обратите внимание, что атрибуты, которые вы ранее задали с помощью метода `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\} Вы можете задавать собственные пользовательские атрибуты — как правило, они связаны с использованием вашего приложения. Например, в фитнес-приложениях это может быть количество тренировок в неделю, в приложениях для изучения языков — уровень знаний пользователя и так далее. Атрибуты можно использовать в сегментах для создания таргетированных пейволов и предложений, а также в аналитике, чтобы понять, какие продуктовые метрики сильнее всего влияют на выручку. ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Помните, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на одного пользователя - Длина имени ключа — не более 30 символов. Допускаются буквенно-цифровые символы, а также: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: flutter-listen-subscription-changes --- --- title: "Проверка статуса подписки в Flutter SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем Flutter-приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде. Вместо этого достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка.
Перед тем как проверять статус подписки (нажмите, чтобы развернуть) - Для iOS настройте [App Store Server Notifications](enable-app-store-server-notifications) - Для Android настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn)
## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Рекомендуем получать профиль при запуске приложения — например, когда вы [идентифицируете пользователя](flutter-identifying-users#setting-customer-user-id-on-configuration) — и обновлять его при каждом изменении. Так вы сможете использовать объект профиля без лишних запросов. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Отслеживание изменений профиля, включая уровни доступа](flutter-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | | --------- | ------------------------------------------------------------ | | Profile |

Объект [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Как правило, достаточно проверить статус уровня доступа профиля, чтобы определить, есть ли у пользователя премиум-доступ к приложению.

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

| Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, если у вас газетное приложение и вы продаёте подписки на разные темы независимо друг от друга, можно создать уровни доступа «sports» и «science». Но в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень доступа по умолчанию «premium». Вот пример проверки уровня доступа «premium» по умолчанию: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Отслеживание обновлений статуса подписки \{#listening-for-subscription-status-updates\} Adapty отправляет событие каждый раз, когда подписка пользователя изменяется. Чтобы получать сообщения от Adapty, выполните дополнительную настройку: ```dart showLineNumbers Adapty().didUpdateProfileStream.listen((profile) { // handle any changes to subscription state }); ``` Adapty также отправляет событие при запуске приложения — в этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш в Adapty SDK хранит статус подписки профиля. Это значит, что даже при недоступности сервера можно получить кэшированные данные о статусе подписки профиля. Однако важно учитывать, что прямые запросы данных из кэша невозможны. SDK периодически обращается к серверу каждую минуту, чтобы проверить наличие обновлений или изменений, связанных с профилем. Если появятся какие-либо изменения — например, новые транзакции или другие обновления — они будут отправлены в кэшированные данные, чтобы поддерживать их синхронизацию с сервером. --- # File: flutter-deal-with-att --- --- title: "Работа с ATT во Flutter SDK" description: "Начните работу с Adapty на Flutter для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и запрашивает у пользователя разрешение на отслеживание, необходимо передать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning Настоятельно рекомендуем отправлять это значение как можно раньше при каждом его изменении — только в этом случае данные будут своевременно переданы в настроенные вами интеграции. ::: --- # File: kids-mode-flutter --- --- title: "Режим Kids Mode во Flutter SDK" description: "Легко включите Kids Mode для соответствия политикам Apple и Google. IDFA, GAID и рекламные данные не собираются во Flutter SDK." --- Если ваше Flutter-приложение предназначено для детей, необходимо соблюдать политики [Apple](https://developer.apple.com/kids/) и [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими требованиями и успешно пройти проверку в стор. ## Что нужно настроить? \{#whats-required\} Необходимо настроить SDK, чтобы отключить сбор следующих данных: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адрес](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно подходить к выбору customer user ID. Идентификатор в формате `` однозначно будет расценён как сбор персональных данных — так же, как и использование email. Для Kids Mode лучшей практикой является использование случайных или анонимизированных идентификаторов (например, хешированных ID или UUID, сгенерированных на устройстве) для обеспечения соответствия требованиям. ## Включение Kids Mode \{#enabling-kids-mode\} ### Настройки в дашборде Adapty \{#updates-in-the-adapty-dashboard\} В дашборде Adapty необходимо отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения \{#updates-in-your-mobile-app-code\} В соответствии с требованиями политик отключите сбор IDFA пользователя (для iOS), GAID/AAID (для Android) и IP-адреса. **Android: Обновите конфигурацию SDK** ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` ..withIpAddressCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` **iOS: включение Kids Mode в SDK v4** :::important В SDK v4 нативный iOS SDK устанавливается через Swift Package Manager, а Kids Mode включается через трейт `KidsMode` Swift Package, который исключает из компиляции весь код IDFA, AdSupport и AppTrackingTransparency. Для этого требуется **Xcode 26** или выше. ::: В SDK v4 используйте пакет `adapty_flutter_kids` вместо `adapty_flutter` в файле `pubspec.yaml`. Это вариант плагина с Kids Mode — с тем же публичным API и той же версией; единственное отличие в том, что его нативный iOS SDK собран с трейтом `KidsMode`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Ваш код на Dart остаётся прежним — нужно лишь обновить импорт на новое имя пакета: ```dart showLineNumbers title="Dart" ``` **iOS: включение Kids Mode через CocoaPods (SDK v3)** 1. Обновите Podfile: - Если у вас **нет** секции `post_install` — добавьте весь блок кода ниже целиком. - Если секция `post_install` **есть** — добавьте в неё выделенные строки. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Примените изменения, выполнив команду ```sh showLineNumbers title="Shell" pod install ``` --- # File: flutter-get-onboardings --- --- title: "Получение онбордингов в Flutter SDK" description: "Узнайте, как получать онбординги в Adapty для Flutter." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений или улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — обеспечивая более плавную анимацию, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Paywall Builder на дашборде Adapty, его можно отобразить в Flutter-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Перед началом убедитесь, что: 1. Установлен [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Онбординг добавлен в [плейсмент](placements). ## Загрузка онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется в виде контейнера с конфигурацией, которую приложение должно загрузить и отобразить. Этот контейнер управляет всем процессом: какой контент показывать, как его представлять и как обрабатывать действия пользователя (например, ответы на вопросы викторины или данные из форм). Контейнер также автоматически отслеживает события аналитики, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности загружайте конфигурацию онбординга заранее — чтобы изображения успели скачаться до того, как пользователь увидит онбординг. Чтобы получить онбординг, используйте метод `getOnboarding`: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboarding(placementId: "YOUR_PLACEMENT_ID"); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` Затем вызовите метод `createOnboardingView`, чтобы получить отображение, которое вы будете показывать. :::warning Результат метода `createOnboardingView` можно использовать только один раз. Если вам нужно использовать его повторно, вызовите метод `createOnboardingView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers try { final onboardingView = await Adapty().createOnboardingView(onboarding: onboarding); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

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

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

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

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

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

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

| Параметры ответа: | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html), содержащий: идентификатор и конфигурацию онбординга, Remote Config и ряд других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а у пользователей слабый интернет, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях удобно показывать онбординг по умолчанию — чтобы пользователь не видел пустой экран, а получал полноценный опыт. Чтобы решить эту задачу, используйте метод `getOnboardingForDefaultAudience`, который получает онбординг для указанного плейсмента из аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг через метод `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Рекомендуется использовать `getOnboarding` вместо `getOnboardingForDefaultAudience`, поскольку последний имеет важные ограничения: - **Проблемы совместимости**: могут возникнуть сложности при поддержке нескольких версий приложения — придётся либо делать обратно совместимый дизайн, либо мириться с тем, что старые версии будут отображать онбординг некорректно. - **Нет персонализации**: отображается только контент для аудитории «Все пользователи», без таргетинга по стране, атрибуции или пользовательским атрибутам. Если для вашего случая скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` Параметры: | Параметр | Наличие | Описание | |-----------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

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

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

|

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

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

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

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

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

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

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

| --- # File: flutter-present-onboardings --- --- title: "Отображение онбординга во Flutter SDK" description: "Узнайте, как эффективно отображать онбординги для повышения конверсии." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это даёт более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: Если вы настроили онбординг с помощью билдера, вам не нужно беспокоиться о его рендеринге в коде Flutter-приложения — всё отображение уже описано внутри самого онбординга. Он содержит как то, что нужно показать, так и то, как это должно выглядеть. Перед началом убедитесь, что: 1. Вы установили [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Adapty Flutter SDK предоставляет два способа отображения онбордингов: - **Отдельный экран (Standalone screen)** - **Встроенный виджет (Embedded widget)** ## Отображение как отдельный экран \{#present-as-standalone-screen\} Чтобы отобразить онбординг как отдельный экран, вызовите метод `onboardingView.present()` на объекте `onboardingView`, созданном методом `createOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно снова показать онбординг, вызовите `createOnboardingView` ещё раз, чтобы создать новый экземпляр `onboardingView`. :::warning Повторное использование того же `onboardingView` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Закрытие онбординга \{#dismiss-the-onboarding\} Чтобы программно закрыть онбординг, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить онбординг в существующее дерево виджетов, используйте виджет `AdaptyUIOnboardingPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note Чтобы платформенный виджет на Android работал корректно, убедитесь, что ваш `MainActivity` наследует `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## Загрузка во время онбординга \{#loader-during-onboarding\} При отображении онбординга между сплэш-экраном и самим онбордингом может появиться короткий экран загрузки — пока инициализируется базовое представление. Это можно обработать по-разному в зависимости от ваших потребностей. #### Управление сплэш-экраном через onDidFinishLoading \{#control-splash-screen-using-ondidfinishloading\} :::note Этот подход доступен только при встраивании онбординга как виджета. Для отображения в виде отдельного экрана он недоступен. ::: Рекомендуемый кросс-платформенный подход — держать сплэш-экран или собственный оверлей видимым до тех пор, пока онбординг полностью не загрузится, а затем скрыть его вручную. При использовании встроенного виджета разместите свой виджет поверх него и скройте оверлей, когда сработает `onDidFinishLoading`: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### Кастомизация нативного загрузчика \{#customize-native-loader\} :::important Этот подход зависит от платформы и требует поддержки нативного UI-кода. Не рекомендуется, если вы не поддерживаете отдельные нативные слои в своём приложении. ::: Если нужно кастомизировать сам загрузчик по умолчанию, его можно заменить платформо-специфичными макетами. Этот подход требует отдельных реализаций для Android и iOS: - **iOS**: добавьте `AdaptyOnboardingPlaceholderView.xib` в ваш Xcode-проект - **Android**: создайте `adapty_onboarding_placeholder_view.xml` в `res/layout` и определите там заглушку ## Настройка открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} :::important Настройка открытия ссылок в онбордингах поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает бесшовный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, и пользователю не нужно переключаться между приложениями. Если вы хотите открывать ссылки во внешнем браузере, вы можете изменить это поведение, задав параметру `externalUrlsPresentation` значение `AdaptyWebPresentation.externalBrowser`: ```dart showLineNumbers title="Flutter" final onboardingView = await AdaptyUI().createOnboardingView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser ); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` ## Отключение отступов для безопасной зоны (Android) \{#disable-safe-area-paddings-android\} По умолчанию на Android-устройствах экран онбординга автоматически добавляет отступы для безопасной зоны, чтобы не перекрывать системные элементы интерфейса — строку состояния и навигационную панель. Если вы хотите отключить это поведение и самостоятельно управлять разметкой, добавьте булев ресурс в ваше приложение: 1. Перейдите в `android/app/src/main/res/values`. Если файл `bools.xml` отсутствует, создайте его. 2. Добавьте следующий ресурс: ```xml false ``` Обратите внимание, что изменения применяются глобально для всех онбордингов в вашем приложении. --- # File: flutter-handling-onboarding-events --- --- title: "Обработка событий онбординга в Flutter SDK" description: "Обработка событий онбординга во Flutter с помощью Adapty." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавные анимации, единообразный нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls), чтобы начать работу. ::: Онбординги, настроенные с помощью билдера, генерируют события, на которые может реагировать ваше приложение. Способ обработки этих событий зависит от выбранного подхода к отображению: - **Полноэкранное отображение**: требует настройки глобального наблюдателя событий, который обрабатывает события для всех представлений онбординга - **Встроенный виджет**: обрабатывает события через параметры обратного вызова прямо в виджете Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## События полноэкранного отображения \{#full-screen-presentation-events\} ### Настройка наблюдателя событий \{#set-up-event-observer\} Чтобы обрабатывать события для полноэкранных онбордингов, реализуйте `AdaptyUIOnboardingsEventsObserver` и установите его перед отображением: ```dart showLineNumbers title="Flutter" AdaptyUI().setOnboardingsEventsObserver(this); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Обработка событий \{#handle-events\} Реализуйте следующие методы в вашем обработчике: ```dart showLineNumbers title="Flutter" void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { // Onboarding finished loading } void onboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error, ) { // Handle loading errors } void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle close action view.dismiss(); } void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle custom actions } void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle user input updates } void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { // Track analytics events } ``` ## События встроенного виджета \{#embedded-widget-events\} При использовании `AdaptyUIOnboardingPlatformView` вы можете обрабатывать события через встроенные параметры обратного вызова непосредственно в виджете. Обратите внимание, что события отправляются как в колбэки виджета, так и в глобальный наблюдатель (если он настроен), однако глобальный наблюдатель необязателен: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Onboarding finished loading }, onDidFailWithError: (error) { // Handle loading errors }, onCloseAction: (meta, actionId) { // Handle close action }, onPaywallAction: (meta, actionId) { _openPaywall(actionId); }, onCustomAction: (meta, actionId) { // Handle custom actions }, onStateUpdatedAction: (meta, elementId, params) { // Handle user input updates }, onAnalyticsEvent: (meta, event) { // Track analytics events }, ) ``` ## Типы событий \{#event-types\} В следующих разделах описаны различные типы событий, которые можно обрабатывать независимо от выбранного подхода к отображению. ### Обработка пользовательских действий \{#handle-custom-actions\} В конструкторе вы можете добавить **пользовательское** действие к кнопке и назначить ему идентификатор. Затем этот ID можно использовать в коде и обрабатывать как пользовательское действие. Например, если пользователь нажимает кастомную кнопку — **Login** или **Allow notifications** — сработает метод делегата `onboardingController` с кейсом `.custom(id:)`, а параметр `actionId` будет равен **Action ID** из билдера. Вы можете создавать собственные ID, например `"allowNotifications"`. ```dart // Full-screen presentation void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { switch (actionId) { case 'login': _login(); break; case 'allow_notifications': _allowNotifications(); break; } } // Embedded widget onCustomAction: (meta, actionId) { _handleCustomAction(actionId); } ```
Пример события (нажмите, чтобы развернуть) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
### Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг заканчивает загрузку, срабатывает это событие: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { print('Onboarding loaded: ${meta.onboardingId}'); } // Embedded widget onDidFinishLoading: (meta) { print('Onboarding loaded: ${meta.onboardingId}'); } ```
Пример события (нажмите, чтобы раскрыть) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
### Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. :::important Обратите внимание: вам нужно самостоятельно управлять тем, что происходит при закрытии онбординга пользователем. Например, нужно прекратить отображение самого онбординга. ::: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { await view.dismiss(); } // Embedded widget onCloseAction: (meta, actionId) { Navigator.of(context).pop(); } ```
Пример события (нажмите, чтобы раскрыть) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
### Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработать действие закрытия и открыть пейвол, не полагаясь на данные события. ::: Самый удобный подход при работе с пейволами в онбординге — сделать ID действия равным ID плейсмента пейвола: Обратите внимание: на iOS одновременно на экране может отображаться только один экран (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, вы не можете программно управлять онбордингом в фоне. Попытка закрыть онбординг закроет пейвол, оставив онбординг видимым. Чтобы этого избежать, всегда закрывайте экран онбординга перед показом пейвола. ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } Future _openPaywall(String actionId) async { // Implement your paywall opening logic here } // Embedded widget onPaywallAction: (meta, actionId) { _openPaywall(actionId); } ```
Пример события (нажмите, чтобы развернуть) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
### Отслеживание навигации \{#tracking-navigation\} Аналитическое событие поступает при различных навигационных событиях в онбординге: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { trackEvent(event.type, meta.onboardingId); } // Embedded widget onAnalyticsEvent: (meta, event) { trackEvent(event.type, meta.onboardingId); } ``` Объект `event` может быть одного из следующих типов: | Тип | Описание | |------------|-------------| | `onboardingStarted` | Когда онбординг загружен | | `screenPresented` | Когда показан любой экран | | `screenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `secondScreenPresented` | Когда показан второй экран | | `userEmailCollected` | Срабатывает, когда email пользователя собран через поле ввода | | `onboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, [назначьте идентификатор `final` последнему экрану](design-onboarding). | | `unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит `meta`-информацию: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана в флоу | | `screensTotal` | Общее количество экранов во флоу |
Примеры событий (нажмите, чтобы развернуть) ```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: flutter-onboarding-input --- --- title: "Обработка данных онбординга во Flutter SDK" description: "Сохраняйте и используйте данные онбординга в Flutter-приложении с помощью Adapty SDK." --- :::warning **Онбординги объявлены устаревшими в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее см. в разделах [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранить или обработать тип поля в своём коде. Например: ```dart // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Process data } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Process data } ``` Узнайте о формате action [здесь](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyUIOnboardingPlatformView/onStateUpdatedAction.html).
Форма свойств для каждого типа params (нажмите, чтобы развернуть) ```dart void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // elementId — это String: elementId; // 'preference_selector' // meta — AdaptyUIOnboardingMeta: meta.onboardingId; // 'onboarding_123' meta.screenClientId; // 'preferences_screen' meta.screenIndex; // 1 meta.screensTotal; // 3 // params — один из подклассов AdaptyOnboardingsStateUpdatedParams: switch (params) { case AdaptyOnboardingsSelectParams(:final id, :final value, :final label): // одна выбранная опция id; // 'option_1' value; // 'premium' label; // 'Premium Plan' break; case AdaptyOnboardingsMultiSelectParams(:final params): // список выбранных опций, каждая из которых является AdaptyOnboardingsSelectParams params; // [(id: 'interest_1', value: 'sports', label: 'Sports'), (id: 'interest_2', value: 'music', label: 'Music')] break; case AdaptyOnboardingsInputParams(:final input): switch (input) { case AdaptyOnboardingsTextInput(:final value): value; // 'John Doe' break; case AdaptyOnboardingsEmailInput(:final value): value; // 'user@example.com' break; case AdaptyOnboardingsNumberInput(:final value): value; // 25.0 (double) break; } break; case AdaptyOnboardingsDatePickerParams(:final day, :final month, :final year): day; // 15 month; // 6 year; // 1990 break; } } ```
## Сценарии использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](flutter-setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите сохранить это значение как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` ### Настройте пейволы на основе ответов \{#customize-paywalls-based-on-answers\} С помощью квизов в онбординге вы можете настраивать пейволы, которые показываете пользователям после завершения онбординга. Например, можно спросить пользователей об их опыте в спорте и показывать разные CTA и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте понятные идентификаторы его вариантам ответов. 2. Обработайте ответы квиза по их идентификаторам и [задайте пользовательские атрибуты](flutter-setting-user-attributes) для пользователей. ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` 3. [Создайте сегменты](segments) для каждого значения пользовательского атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](flutter-paywalls) для плейсмента в коде приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](flutter-handling-onboarding-events#opening-a-paywall). --- # File: flutter-sdk-call-order --- --- title: "Порядок вызовов в Flutter SDK" description: "Избегайте потери платного доступа, пропущенной атрибуции и случайных ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `Adapty().activate()` должен завершиться до вызова любых других методов Adapty SDK. До его завершения SDK не имеет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой [`#2002 notActivated`](error-handling-on-flutter-react-native-unity#custom-network-codes). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty().identify()` в этот момент. Не вызывайте методы, связанные с действиями пользователя, пока `identify` не завершится. Вызовы, которые выполняются параллельно с ним, либо завершатся с ошибкой [`#3006 profileWasChanged`](error-handling-on-flutter-react-native-unity#custom-network-codes), либо применятся к анонимному профилю, созданному при активации. В этом случае атрибуция, MMP ID вроде `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, затем вызывайте методы SDK. - **Шаги 1 и 3**: нужны только при интеграции MMP или аналитического SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если вы знаете customer user ID в момент запуска приложения, передайте его напрямую в `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте SDK вашего MMP или аналитики (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь callback с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty().activate(configuration: ...)` с `withCustomerUserId`, заданным в конфигурации | При запуске приложения, после шага 1, если customer user ID известен | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty().activate(configuration: ...)` без `withCustomerUserId` | При запуске приложения, после шага 1, если customer user ID неизвестен (или не используется) | Adapty создаёт анонимный профиль. | | 3 | `Adapty().setIntegrationIdentifier(key: ..., value: ...)` для каждого MMP | После шага 2, до любого вызова, инициированного действием пользователя | Необходимо, чтобы идентификаторы MMP попали в правильный профиль. | | 4 | `await Adapty().identify(customerUserId)` | После шага 3 (или шага 2, если нет MMP), до шага 5 — только на пути 2b с аутентификацией | Всегда используйте `await`. Параллельные вызовы во время `identify` приводят к ошибке `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` в SDK v4), `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), передайте его напрямую в `activate()`. В противном случае веб-покупка будет недоступна на устройстве, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Сведения о метаданных, которые нужно передавать при каждом веб-чекауте, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: flutter-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейволов в Flutter SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кэширование и резервные паттерны для Flutter." --- Надёжная загрузка пейвола во Flutter решает три задачи: быстрый рендер, возврат пейвола с нужной аудиторией и корректный фолбэк при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные паттерны для достижения этого. :::tip Предполагается, что `Adapty().activate()` и `Adapty().identify()` уже выполнены. См. [Порядок вызовов в Flutter SDK](flutter-sdk-call-order). ::: Советы ниже используют имена методов v3. В SDK v4 `getPaywall` переименован в `getFlow`, а тип политики получения — в `AdaptyFlowFetchPolicy` — все правила применяются без изменений. ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Предзагружайте все плейсменты параллельно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время всплеска нагрузки. | | Вызывайте `getPaywall` после того, как атрибуция успеет разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `didUpdateProfileStream`. | Вызывайте `getPaywall` в `main()` до `runApp`. | Атрибуция ещё не получена. Пейвол разрешается по дефолтной аудитории и молча обходит сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ожидайте `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или закрывают приложение. | См. [Получение пейволов и продуктов](fetch-paywalls-and-products-flutter) для справки по параметрам `fetchPolicy` и `loadTimeout`, а также [Плейсменты](placements) для выбора подходящего плейсмента. ## Настройте приложение для работы при слабом соединении \{#tune-for-poor-connectivity\} Для рынков с устойчиво плохим качеством связи (сельские районы, транспортные узлы, регионы с проблемной маршрутизацией): - Используйте `fetchPolicy: AdaptyPaywallFetchPolicy.returnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Установите `loadTimeout` в 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола вызовом `getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не задерживала интерфейс. --- # File: flutter-show-aa-targeted-paywall --- --- title: "Показ пейвола с таргетингом Apple Ads при первом запуске во Flutter SDK" description: "Ненадолго подождите атрибуцию Apple Ads перед показом пейвола при первом запуске во Flutter, возвращаясь к аудитории по умолчанию при истечении таймаута. Использует AdaptyProfile.appliedAttributionSources." --- Атрибуция Apple Ads (AA) поступает асинхронно после вызова `Adapty().activate()`. При первом запуске она, как правило, ещё не получена, поэтому если сразу вызвать `getPaywall`, Adapty обработает запрос по аудитории по умолчанию, и пользователи Apple Ads не увидят пейвол, настроенный для AA-сегмента. Вместо того чтобы показывать один пейвол, а затем заменять его другим, подождите немного, пока не придёт атрибуция AA: если она поступит в течение короткого таймаута — покажите целевой пейвол, если нет — пейвол аудитории по умолчанию. `AdaptyProfile.appliedAttributionSources` сообщает, когда атрибуция AA была применена. ## Прежде чем начать \{#before-you-start\} Вам понадобится: - Adapty Flutter SDK **3.17.0** или новее. - Apple Ads, настроенный для приложения в Adapty. См. [Apple Ads](apple-search-ads). ## Как это работает \{#how-it-works\} После `Adapty().activate()` SDK в фоне запрашивает у Apple данные атрибуции Apple Ads и передаёт результат на сервер Adapty. Когда AA становится активным источником атрибуции для профиля, SDK доставляет обновлённый `AdaptyProfile` в слушатель `didUpdateProfileStream`, а в списке `appliedAttributionSources` появляется `AdaptyAttributionSource.appleAds`. При первом запуске возможны два исхода: 1. **Атрибуция поступает в рамках таймаута.** Вызовите `getPaywall` — Adapty обработает запрос с учётом аудитории Apple Ads и вернёт целевой пейвол. 2. **Таймаут истекает первым.** В этом случае покажите пейвол для аудитории по умолчанию — пользователи без атрибуции Apple Ads не будут ждать. `getPaywallForDefaultAudience` вернёт его без ожидания сегментации. `appliedAttributionSources` может быть пустым. Это означает одно из двух: - атрибуция Apple Ads ещё не обработана для этого профиля, или - атрибуция не поступила вовсе. В любом случае вызов `getPaywallForDefaultAudience` безопасен — он возвращает пейвол для аудитории по умолчанию вне зависимости от состояния профиля. :::important Ожидание актуально только при первом запуске. После того как атрибуция Apple Ads записана, она постоянно хранится в профиле. При каждом последующем запуске кешированный профиль уже содержит `AdaptyAttributionSource.appleAds` в `appliedAttributionSources`, поэтому путь атрибуции разрешается немедленно и `getPaywall` возвращает пейвол для сегмента Apple Ads без какой-либо задержки. ::: ## Реализация \{#implementation\} При первом запуске дождитесь `AdaptyAttributionSource.appleAds` и установите жёсткий таймаут — если атрибуция Apple Ads так и не пришла, эти пользователи всё равно должны увидеть пейвол. 1. **Активируйте SDK.** См. [Установка и настройка Flutter SDK](sdk-installation-flutter). 2. **Подпишитесь на обновления профиля** с помощью `Adapty().didUpdateProfileStream.listen(…)`. Если вы ещё не настроили слушатель, см. [Отслеживание обновлений подписки](flutter-check-subscription-status#listen-to-subscription-updates). 3. **Отслеживайте `AdaptyAttributionSource.appleAds` в `appliedAttributionSources`.** Когда он появится, загрузите пейвол с помощью `getPaywall` — Adapty вернёт вариант, сегментированный по AA: ```dart final subscription = Adapty().didUpdateProfileStream.listen((profile) async { if (!profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) return; final paywall = await Adapty().getPaywall(placementId: placementId); // present the segmented paywall, then cancel the subscription and the timer }); ``` `didUpdateProfileStream` — это широковещательный поток без повтора событий, поэтому также проверяйте текущий профиль через `getProfile()`. При повторных запусках приложения сохранённая атрибуция уже применена и повторно не отправляется. 4. **Запустите таймер на 3–5 секунд параллельно с подпиской.** Если таймер сработает раньше, чем появится `AdaptyAttributionSource.appleAds`, загрузите пейвол для аудитории по умолчанию с помощью `getPaywallForDefaultAudience`. Отобразите тот пейвол, который разрешится первым, и отмените второй запрос, чтобы пейвол не загружался дважды. Настройте [резервный пейвол](flutter-use-fallback-paywalls) для плейсмента, чтобы пользователь не остался ни с чем при сбое сетевого запроса. ## Полный пример \{#complete-example\} Реализация ниже запускает гонку между получением атрибуции и таймаутом, параллельно подгружает пейвол для аудитории по умолчанию и возвращает нужный пейвол. Вызывающий код ждёт одну функцию — никаких слушателей и флагов состояния на стороне вызова: - Если атрибуция приходит раньше `timeout`, возвращается сегментированный пейвол через `getPaywall`. - Если первым срабатывает `timeout`, возвращается заранее загруженный пейвол для аудитории по умолчанию через `getPaywallForDefaultAudience`. ```dart title="apple_ads_paywall.dart" /// Returns the Apple Ads-segmented paywall if attribution is applied within /// [timeout], otherwise the default-audience paywall. Call after Adapty().activate(). Future getPaywallOrDefault({ required String placementId, required Duration timeout, }) { // Prefetch the default-audience paywall right away so the timeout path resolves // without an extra network round-trip. `getPaywallForDefaultAudience` skips the // wait for segmentation data. `..ignore()` keeps an unused prefetch from surfacing // as an unhandled error; the error still reaches the caller if this paywall wins. final defaultPaywall = Adapty().getPaywallForDefaultAudience(placementId: placementId)..ignore(); final completer = Completer(); late final StreamSubscription subscription; late final Timer timer; void resolve(Future paywall) { if (completer.isCompleted) return; timer.cancel(); subscription.cancel(); completer.complete(paywall); } void onProfile(AdaptyProfile profile) { if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) { resolve(Adapty().getPaywall(placementId: placementId)); } } // Attribution path: react to profile updates as attribution is applied. subscription = Adapty().didUpdateProfileStream.listen(onProfile); // The stream is a broadcast stream and doesn't replay, so check the current // profile too — on relaunches attribution is already stored and won't re-emit. Adapty().getProfile().then(onProfile).ignore(); // Timeout path: fall back to the prefetched default-audience paywall. timer = Timer(timeout, () => resolve(defaultPaywall)); return completer.future; } ``` Вызывайте этот метод с экрана-заставки, а затем отображайте пейвол после получения результата: ```dart try { final paywall = await getPaywallOrDefault( placementId: 'YOUR_PLACEMENT_ID', timeout: const Duration(seconds: 5), ); // present the paywall } on AdaptyError catch (adaptyError) { // handle the error or show a fallback paywall } catch (e) { // handle the error } ``` Настройте `timeout` под то, сколько времени вы готовы заставлять пользователей ждать перед показом пейвола. У большинства пользователей нет атрибуции Apple Ads, поэтому они ждут всё отведённое время — 3–5 секунд — разумный баланс. Атрибуция, если она приходит, обычно поступает в течение нескольких секунд после запуска. Если ваше приложение уже слушает `didUpdateProfileStream` для других целей (например, [проверки статуса подписки](flutter-check-subscription-status#listen-to-subscription-updates)), менять ничего не нужно. `didUpdateProfileStream` — это широковещательный поток (broadcast stream), поэтому он поддерживает несколько независимых слушателей, не мешая друг другу. --- # File: flutter-test --- --- title: "Тестирование и релиз в Flutter SDK" description: "Узнайте, как проверить статус подписки в Flutter-приложении с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё Flutter-приложение, вам нужно убедиться, что всё настроено правильно и покупки работают корректно на платформах iOS и Android. Это включает тестирование как интеграции SDK, так и самого процесса покупки в песочнице Apple и тестовой среде Google Play. ## Тестирование приложения \{#test-your-app\} Подробное руководство по тестированию встроенных покупок доступно в гайдах для каждой платформы: [гайд по тестированию на iOS](test-purchases-in-sandbox) и [гайд по тестированию на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдите по [чеклисту для релиза](release-checklist) и проверьте: - Настроены ли подключение к стору и серверные уведомления - Завершаются ли покупки и передаются ли данные в Adapty - Корректно ли открывается и восстанавливается доступ - Выполнены ли требования к конфиденциальности и ревью --- # File: InvalidProductIdentifiers-flutter --- --- title: "Исправление ошибки Code-1000 noProductIDsFound в Flutter SDK" description: "Устраните ошибку с недопустимым идентификатором продукта при управлении подписками в Adapty." --- Ошибка с кодом 1000 — `noProductIDsFound` — означает, что ни один из продуктов, запрошенных на пейволе, недоступен для покупки в App Store, хотя они там перечислены. Иногда эта ошибка сопровождается предупреждением `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки, можно его проигнорировать. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверьте продукты \{#step-3-check-products\} 1. Откройте **App Store Connect** и перейдите в раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в левом меню. 2. Нажмите на название группы подписок. Ваши продукты будут перечислены в разделе **Subscriptions**. 3. Убедитесь, что тестируемый продукт имеет статус **Ready to Submit**. 4. Сравните ID продукта из таблицы с тем, что указан на вкладке [**Products**](https://app.adapty.io/products) в дашборде Adapty. Если ID не совпадают, скопируйте ID продукта из таблицы и [создайте продукт](create-product) с этим ID в дашборде Adapty. ## Шаг 3. Проверьте доступность продукта \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте тот же раздел **Subscriptions**. 2. Нажмите на название группы подписок, чтобы просмотреть продукты. 3. Выберите тестируемый продукт. 4. Прокрутите до раздела **Availability** и убедитесь, что все необходимые страны и регионы указаны. ## Шаг 4. Проверьте цены продукта \{#step-5-check-product-prices\} 1. Снова перейдите в раздел **Monetization** → **Subscriptions** в **App Store Connect**. 2. Нажмите на название группы подписок. 3. Выберите тестируемый продукт. 4. Прокрутите вниз до раздела **Subscription Pricing** и раскройте секцию **Current Pricing for New Subscribers**. 5. Убедитесь, что все необходимые цены указаны. ## Шаг 5. Убедитесь, что статус платного приложения, банковский счёт и налоговые формы активны \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. 2. Выберите название своей компании. 3. Прокрутите вниз и убедитесь, что **Paid Apps Agreement**, **Bank Account** и **Tax forms** имеют статус **Active**. Выполнив эти шаги, вы должны устранить предупреждение `InvalidProductIdentifiers` и сделать продукты доступными в сторе. ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, действительный API-ключ — а SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт может быть завис в реестре Apple. Иногда реестр продуктов Apple входит в состояние, при котором продукт существует в интерфейсе App Store Connect, но недоступен через путь поиска StoreKit. Удалите продукт в App Store Connect и пересоздайте его с тем же ID. После пересоздания подождите до 24 часов для распространения изменений. --- # File: cantMakePayments-flutter --- --- title: "Исправление ошибки Code-1003 cantMakePayment в Flutter SDK" description: "Решение ошибки при проведении платежей при управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: migration-to-flutter-sdk-v4 --- --- title: "Миграция Adapty Flutter SDK на версию 4.0" description: "Перейдите на Adapty Flutter SDK v4.0, заменив paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty Flutter SDK 4.0 вводит флоу и соответственно переименовывает paywall API. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty().getPaywall(placementId: id)` | `Adapty().getFlow(placementId: id)` | | `Adapty().getPaywallForDefaultAudience(placementId: id)` | `Adapty().getFlowForDefaultAudience(placementId: id)` | | `Adapty().getPaywallProducts(paywall: paywall)` | `Adapty().getPaywallProducts(flow: flow)` | | `Adapty().logShowPaywall(paywall: paywall)` | `Adapty().logShowFlow(flow: flow)` | | `AdaptyPaywall` (тип) | `AdaptyFlow` | | `AdaptyPaywallFetchPolicy` (тип) | `AdaptyFlowFetchPolicy` | | `AdaptyUI().createPaywallView(paywall: paywall)` | `AdaptyUI().createFlowView(flow: flow)` | | `AdaptyUIPaywallView` (тип) | `AdaptyUIFlowView` | | `AdaptyUIPaywallPlatformView` (виджет) | `AdaptyUIFlowPlatformView` | | `AdaptyUI().presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI().presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUI().setPaywallsEventsObserver(observer)` | `AdaptyUI().setFlowsEventsObserver(observer)` | | Колбэки `paywallViewDid*` | Колбэки `flowViewDid*` | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, а `getPaywallProducts` теперь принимает `AdaptyFlow`. При получении флоу больше не нужно передавать `locale`. API для покупок и профиля (`makePurchase`, `restorePurchases`, `getProfile`, `identify` и т. д.) остался без изменений, как и методы работы с представлениями `present`, `dismiss` и `showDialog`. Часть поведения по умолчанию изменилась — см. [Изменения поведения по умолчанию](#default-behavior-changes). ## Минимальные требования \{#minimum-versions\} Adapty Flutter SDK 4.0 повышает минимальные требования: - **iOS 15.0** — минимальная цель развёртывания iOS, повышена с iOS 13.0. - **Xcode 26** или новее — нативный iOS SDK использует Swift tools 6.2. - **Flutter 3.32.0** (Dart 3.8.0) или новее. ## Установка \{#installation\} ### Обновите пакет \{#update-the-package\} Какой пакет устанавливать, зависит от того, используется ли в вашем приложении Kids Mode. Для большинства приложений обновите `adapty_flutter` до версии 4.0 в файле `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` Если ваше приложение использует Kids Mode, укажите вместо него `adapty_flutter_kids`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Этот **автономный** пакет удаляет IDFA и код отслеживания рекламы для соответствия требованиям App Store. Обновите путь импорта Dart на `package:adapty_flutter_kids/adapty_flutter.dart`. В остальном миграция полностью идентична обычному пакету. Kids Mode также требует отключения сбора IP-адресов в дашборде Adapty — полную инструкцию по настройке см. в разделе [Kids Mode](kids-mode-flutter). ### iOS: нативные SDK теперь поставляются через Swift Package Manager \{#ios-native-sdks-now-come-through-swift-package-manager\} [Репозиторий спецификаций CocoaPods становится доступным только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), поэтому начиная с v4 нативный iOS SDK **больше не распространяется через CocoaPods** — плагин получает его только через **Swift Package Manager**. Если вы используете Flutter 3.32–3.43, один раз включите поддержку Swift Package Manager: ```bash flutter config --enable-swift-package-manager ``` В Flutter 3.44 и выше Swift Package Manager включён по умолчанию, так что никаких дополнительных действий не требуется. ## Получение флоу \{#fetching-flows\} ### getPaywall → getFlow Возвращаемый тип меняется с `AdaptyPaywall` на `AdaptyFlow`, и теперь не нужно передавать `locale` — при отображении флоу локализация определяется автоматически; для кастомных пейволов все настроенные локали возвращаются в `flow.remoteConfigs`: ```diff showLineNumbers - final paywall = await Adapty().getPaywall(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` переименовывается аналогично: ```diff showLineNumbers - final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); ``` Тип политики загрузки переименован с `AdaptyPaywallFetchPolicy` на `AdaptyFlowFetchPolicy`; его варианты (`reloadRevalidatingCacheData`, `returnCacheDataElseLoad`, `returnCacheDataIfNotExpiredElseLoad`) остались без изменений. ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` сохраняет своё название, но теперь принимает `AdaptyFlow` через параметр `flow`: ```diff showLineNumbers - final products = await Adapty().getPaywallProducts(paywall: paywall); + final products = await Adapty().getPaywallProducts(flow: flow); ``` ## Модель данных \{#data-model\} `getFlow` возвращает `AdaptyFlow` вместо `AdaptyPaywall`, и структура объекта изменилась: | Член `AdaptyPaywall` v3 | Член `AdaptyFlow` v4 | Действие | |---|---|---| | `remoteConfig` (один, nullable) | `remoteConfigs` (список) | Флоу хранит один Remote Config на каждый настроенный язык. Геттер `remoteConfig` по-прежнему существует и возвращает первую запись; чтобы выбрать конкретный язык, найдите нужную запись в `remoteConfigs` по полю `locale`. | | `productIdentifiers` | `productIdentifiers` | Сохранён, но теперь объединяет идентификаторы из всех вариаций пейволов флоу. Идентификаторы для каждой вариации доступны через `flow.paywalls[i].productIdentifiers`. | | `hasViewConfiguration` | `hasViewConfiguration` | Без изменений. | | `placementId` (устарело) | удалён | Используйте `flow.placement.id`. | | `revision` (устарело) | удалён | Используйте `flow.placement.revision`. | | `vendorProductIds` (устарело) | удалён | Используйте `productIdentifiers`. | | _(новое)_ | `paywalls` (список `AdaptyFlowPaywall`) | Каждый элемент — одна вариация пейвола во флоу со своими `name`, `variationId` и `productIdentifiers`. | `AdaptyPaywallViewConfiguration` больше не предоставляется публично — конфигурация представления теперь непрозрачна. Удалите все ссылки на этот тип. ## Методы веб-пейвола \{#web-paywall-methods\} `openWebPaywall` и `createWebPaywallUrl` сохраняют свои названия, но параметр `paywall` теперь принимает `AdaptyFlowPaywall` (вариант флоу) вместо `AdaptyPaywall`. По-прежнему можно передать `AdaptyPaywallProduct`. ```diff showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); - await Adapty().openWebPaywall(paywall: paywall); + if (flow.paywalls.isNotEmpty) { + await Adapty().openWebPaywall(paywall: flow.paywalls[0]); + } ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow`. Событие по-прежнему фиксируется для той же вариации, поэтому существующие метрики воронки и A/B-тестов продолжают работать без изменений в дашборде. ```diff showLineNumbers - await Adapty().logShowPaywall(paywall: paywall); + await Adapty().logShowFlow(flow: flow); ``` Как и в v3, вызывать этот метод при отображении флоу или пейволов, отрисованных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не нужно — Adapty отслеживает эти просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### createPaywallView → createFlowView Переименуйте метод и передайте `AdaptyFlow` через параметр `flow`. Остальные параметры (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) остаются без изменений, как и методы представления `present`, `dismiss` и `showDialog`: ```diff showLineNumbers - final view = await AdaptyUI().createPaywallView(paywall: paywall); + final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); ``` ### AdaptyUIPaywallView → AdaptyUIFlowView Тип представления переименован. Устаревшее свойство `paywallVariationId` удалено — используйте `variationId`: ```diff showLineNumbers - void flowViewDidAppear(AdaptyUIPaywallView view) { + void flowViewDidAppear(AdaptyUIFlowView view) { ``` ### AdaptyUIPaywallPlatformView → AdaptyUIFlowPlatformView Если вы встраиваете представление как виджет в дерево виджетов, переименуйте его и передайте параметр `flow`. Колбэки событий (`onDidAppear`, `onDidFinishPurchase` и так далее) сохраняют свои названия: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall: paywall, + AdaptyUIFlowPlatformView( + flow: flow, onDidFinishPurchase: (view, product, purchaseResult) { /* … */ }, ) ``` :::note Представление флоу, созданное с помощью `createFlowView`, одноразовое: после вызова `dismiss()` оно освобождается из памяти и не может быть показано повторно — вызовите `createFlowView` снова, чтобы отобразить флоу ещё раз. ::: ## Обработка событий \{#handling-events\} Класс-наблюдатель переименован с `AdaptyUIPaywallsEventsObserver` на `AdaptyUIFlowsEventsObserver`, метод его регистрации — с `setPaywallsEventsObserver` на `setFlowsEventsObserver`, а все колбэки `paywallViewDid*` — на `flowViewDid*`: ```diff showLineNumbers - class MyObserver extends AdaptyUIPaywallsEventsObserver { + class MyObserver extends AdaptyUIFlowsEventsObserver { @override - void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { + void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { // … } } - AdaptyUI().setPaywallsEventsObserver(this); + AdaptyUI().setFlowsEventsObserver(this); ``` Три коллбэка теперь **обязательны** — без них наблюдатель не скомпилируется: - **`flowViewDidFinishPurchase`**: В v3 был опциональным — по умолчанию закрывал вью после покупки. Теперь вы сами решаете, что произойдёт: продолжить флоу или вызвать `view.dismiss()`. - **`flowViewDidFinishRestore`**: Обязательный, как и в v3. - **`flowViewDidReceiveError`**: Заменяет `paywallViewDidFailRendering` и теперь также получает другие ошибки вью. Два небольших изменения: - `setFlowsEventsObserver` (и `setOnboardingsEventsObserver`) теперь принимают `null`, чтобы отвязать ранее установленный наблюдатель — SDK больше не удерживает его. - Новый необязательный коллбэк `flowViewDidReceiveAnalyticEvent` зарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют их в ваш код, поэтому реализовывать его не нужно. В v4 также появились возможности, которые можно подключить по желанию: - `AdaptyUI().setObserverModeResolver(...)` с `AdaptyUIObserverModeResolver` — управляет покупками и восстановлениями, инициированными из флоу, когда SDK работает в [режиме Observer](implement-observer-mode-flutter). Ранее это было доступно только в нативных SDK для iOS и Android. См. [Показ флоу в режиме Observer](flutter-present-flows-in-observer-mode). - `AdaptyUI().setSystemRequestsHandler(...)` с `AdaptyUISystemRequestsHandler` — зарезервировано для системных запросов из флоу (запросы разрешений ОС и запросы на отзыв в App Store). Флоу пока не инициируют такие запросы, поэтому регистрировать обработчик не нужно. ## Удалённые API \{#removed-apis\} Эти символы были помечены как устаревшие в версии 3.x и удалены в v4: ### setFallbackPaywalls → setFallback ```diff showLineNumbers - await Adapty().setFallbackPaywalls(assetId); + await Adapty().setFallback(assetId); ``` ### withIdfaCollectionDisabled → withAppleIdfaCollectionDisabled ```diff showLineNumbers configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') - ..withIdfaCollectionDisabled(true), + ..withAppleIdfaCollectionDisabled(true), ``` ### Другие удалённые элементы - **`AdaptyPurchaseResultSuccess.jwsTransaction`**: Используйте `appleJwsTransaction`. - **`AdaptyUIFlowView.paywallVariationId`**: Используйте `variationId`. - **`AdaptyUIObserver` и `AdaptyUI().setObserver(...)`**: Используйте `AdaptyUIFlowsEventsObserver` и `setFlowsEventsObserver(...)`. ## Изменения поведения по умолчанию \{#default-behavior-changes\} Эти изменения не вызывают ошибок компиляции, поэтому проверяйте их во время выполнения: - **Успешная покупка**: В v3 дефолтный `paywallViewDidFinishPurchase` закрывал экран. В v4 `flowViewDidFinishPurchase` обязателен и не имеет реализации по умолчанию — закрывайте экран самостоятельно, если хотите такого поведения. - **Системная кнопка «Назад» на Android**: Она больше не закрывает флоу по умолчанию. Действие передаётся в `flowViewDidPerformAction` как `AndroidSystemBackAction` — обработайте его там, если хотите, чтобы кнопка «Назад» закрывала флоу. - **Открытие URL**: Дефолтный `flowViewDidPerformAction` теперь обрабатывает `OpenUrlAction`, открывая URL нативно (с учётом настройки встроенного или внешнего браузера из дашборда), а также закрывает экран по `CloseAction`. Переопределите коллбэк, чтобы обрабатывать URL самостоятельно. - **Ошибки экрана**: `flowViewDidReceiveError` обязателен, и закрытие экрана зависит от вашей реализации. Если в v3 ваша интеграция рассчитывала на автоматическое закрытие при ошибках рендеринга, вызывайте `view.dismiss()` в этом коллбэке. - **Жизненный цикл экрана**: Закрытие флоу или онбординга освобождает его из памяти. Закрытый экран нельзя показать повторно — создайте новый. ## Устаревший API онбординга \{#onboarding-api-deprecation\} Устаревший API онбординга объявлен устаревшим в v4.0 в пользу [Flow Builder](adapty-flow-builder). Он по-прежнему работает, а IDE помечает устаревшие символы через аннотации `@Deprecated` — никаких предупреждений во время выполнения нет. Эти символы будут удалены в одном из следующих релизов, поэтому планируйте переход ваших онбордингов на Flow Builder. Устаревшие символы: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView`, `presentOnboardingView`, `dismissOnboardingView`, `setOnboardingsEventsObserver`, `AdaptyOnboarding`, `AdaptyUIOnboardingView`, `AdaptyUIOnboardingPlatformView`, `AdaptyUIOnboardingsEventsObserver`, а также модели состояния, ввода и аналитики онбординга. --- # File: flutter-migration-guide-310 --- --- title: "Руководство по миграции на Flutter Adapty SDK 3.10.0" description: "" --- Adapty SDK 3.10.0 — это крупный релиз с рядом улучшений, которые могут потребовать миграции: 1. Обновите метод `makePurchase`, чтобы использовать `AdaptyPurchaseParameters` вместо отдельных параметров. 2. Замените `vendorProductIds` на `productIdentifiers` в модели `AdaptyPaywall`. ## Обновление метода makePurchase \{#update-makepurchase-method\} Метод `makePurchase` теперь принимает `AdaptyPurchaseParameters` вместо отдельных аргументов `subscriptionUpdateParams` и `isOfferPersonalized`. Это обеспечивает более строгую типизацию и упрощает добавление новых параметров покупки в будущем. ```diff showLineNumbers - final purchaseResult = await adapty.makePurchase( - product: product, - subscriptionUpdateParams: subscriptionUpdateParams, - isOfferPersonalized: true, - ); + final parameters = AdaptyPurchaseParametersBuilder() + ..setSubscriptionUpdateParams(subscriptionUpdateParams) + ..setIsOfferPersonalized(true) + ..setObfuscatedAccountId('your-account-id') + ..setObfuscatedProfileId('your-profile-id'); + final purchaseResult = await adapty.makePurchase( + product: product, + parameters: parameters.build(), + ); ``` Если дополнительные параметры не нужны, достаточно написать: ```dart showLineNumbers final purchaseResult = await adapty.makePurchase( product: product, ); ``` ## Обновление использования модели AdaptyPaywall \{#update-adaptypaywall-model-usage\} Свойство `vendorProductIds` устарело и заменено на `productIdentifiers`. Новое свойство возвращает объекты `AdaptyProductIdentifier` вместо обычных строк, что обеспечивает более структурированную информацию о продуктах. ```diff showLineNumbers - paywall.vendorProductIds.map((vendorId) => - ListTextTile(title: vendorId) - ).toList() + paywall.productIdentifiers.map((productId) => + ListTextTile(title: productId.vendorProductId) + ).toList() ``` Объект `AdaptyProductIdentifier` предоставляет доступ к идентификатору продукта через свойство `vendorProductId`, сохраняя прежнюю функциональность и обеспечивая лучшую структуру для будущих улучшений. ## Обратная совместимость \{#backward-compatibility\} Оба изменения обратно совместимы: - Старые параметры в `makePurchase` устарели, но продолжают работать - Свойство `vendorProductIds` устарело, но по-прежнему доступно - Существующий код продолжит работать, однако будут отображаться предупреждения об устаревании Рекомендуем перейти на новые API, чтобы обеспечить совместимость в будущем и воспользоваться преимуществами улучшенной типизации и расширяемости. --- # File: flutter-migration-guide-38 --- --- title: "Миграция Adapty Flutter SDK на v. 3.8" description: "Перейдите на Adapty Flutter SDK v3.8 для улучшения производительности и новых возможностей монетизации." --- Adapty SDK 3.8.0 — это мажорный релиз, который принёс ряд улучшений, требующих выполнения нескольких шагов миграции. 1. Обновите названия класса и методов наблюдателя. 2. Обновите название метода для резервных пейволов. 3. Обновите название класса представления в методах обработки событий. ## Обновление класса и методов наблюдателя \{#update-observer-class-and-method-names\} Класс наблюдателя и метод его регистрации были переименованы: ```diff showLineNumbers - class MyObserver extends AdaptyUIObserver { + class MyObserver extends AdaptyUIPaywallsEventsObserver { @override void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { // Handle action } } // Register observer - AdaptyUI().setObserver(this); + AdaptyUI().setPaywallsEventsObserver(this); ``` ## Обновление названия метода для резервных пейволов \{#update-fallback-paywalls-method-name\} Метод установки резервных пейволов был упрощён: ```diff showLineNumbers try { - await Adapty.setFallbackPaywalls(assetId); + await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Обновление имени класса представления в методах обработки событий \{#update-view-class-name-in-event-handling-methods\} Все методы обработки событий теперь используют новый класс `AdaptyUIPaywallView` вместо `AdaptyUIView`: ```diff showLineNumbers - void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) + void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) - void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile) + void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile) - void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error) + void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) - void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile) + void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) - void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) - void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) + void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) - void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) ``` --- # File: migration-to-flutter-sdk-34 --- --- title: "Миграция Adapty Flutter SDK на v. 3.4" description: "Мигрируйте на Adapty Flutter SDK v3.4 для повышения производительности и доступа к новым функциям монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который вносит изменения, требующие миграции с вашей стороны. ## Обновите файлы резервных пейволов \{#update-fallback-paywall-files\} Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](flutter-use-fallback-paywalls) новыми файлами. ## Обновите реализацию Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, обновите его реализацию. Раньше для передачи транзакций в Adapty использовались разные методы. В новой версии метод `reportTransaction` следует использовать единообразно как на Android, так и на iOS. Этот метод явно передаёт каждую транзакцию в Adapty, гарантируя её распознавание. Если использовался пейвол, передайте variation ID, чтобы привязать транзакцию к нему. :::warning **Не пропускайте передачу информации о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - // every time when calling transaction.finish() - if (Platform.isAndroid) { - try { - await Adapty().restorePurchases(); - } on AdaptyError catch (adaptyError) { - // handle the error - } catch (e) { - } - } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter330 --- --- title: "Миграция Adapty Flutter SDK на v3.3" description: "Перейдите на Adapty Flutter SDK v3.3 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.3.0 — это крупный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться некоторые шаги миграции. 1. Обновите метод предоставления резервных пейволов. 2. Удалите метод `getProductsIntroductoryOfferEligibility`. 3. Обновите настройки интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase и Google Analytics, Mixpanel, OneSignal, Pushwoosh. 4. Обновите реализацию режима Observer. ## Обновление метода для предоставления резервных пейволов \{#update-method-for-providing-fallback-paywalls\} Раньше метод принимал резервный пейвол в виде JSON-строки (`jsonString`), теперь вместо этого он принимает путь к локальному файлу резервного пейвола (`assetId`). ```diff showLineNumbers import 'dart:async' show Future; import 'dart:io' show Platform; -import 'package:flutter/services.dart' show rootBundle; -final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; -final jsonString = await rootBundle.loadString(filePath); +final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { - await adapty.setFallbackPaywalls(jsonString); + await adapty.setFallbackPaywalls(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Полный пример кода смотрите на странице [Использование резервных пейволов](flutter-use-fallback-paywalls). ## Удаление метода `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} До Adapty iOS SDK 3.3.0 объект продукта всегда включал офферы вне зависимости от того, подходит ли для них пользователь. Вам нужно было проверять eligibility вручную перед использованием оффера. Теперь объект продукта включает оффер только в том случае, если пользователь на него подходит. Это значит, что проверять eligibility больше не нужно — если оффер присутствует, пользователь подходит для него. ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Чтобы интеграции корректно работали с Adapty Flutter SDK 3.3.0 и новее, обновите конфигурации SDK для следующих интеграций согласно инструкциям ниже. ### Adjust Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_config.dart'; try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } + await Adapty().setIntegrationIdentifier( + key: "adjust_device_id", + value: adid, + ); final attributionData = await Adjust.getAttribution(); var attribution = Map(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; - Adapty().updateAttribution( - attribution, - source: AdaptyAttributionSource.adjust, - networkUserId: adid, - ); + await Adapty().updateAttribution(attribution, source: "adjust"); } catch (e) { // handle the error } on AdaptyError catch (adaptyError) { // handle the error } ``` ### AirBridge Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import 'package:airbridge_flutter_sdk/airbridge_flutter_sdk.dart'; final deviceUUID = await Airbridge.state.deviceUUID; try { - final builder = AdaptyProfileParametersBuilder() - ..setAirbridgeDeviceId(deviceUUID); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "airbridge_device_id", + value: deviceUUID, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Amplitude Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import 'package:amplitude_flutter/amplitude.dart'; final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); final deviceId = await amplitude.getDeviceId(); final userId = await amplitude.getUserId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setAmplitudeDeviceId(deviceId) - ..setAmplitudeUserId(userId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "amplitude_user_id", + value: userId, + ); + await Adapty().setIntegrationIdentifier( + key: "amplitude_device_id", + value: deviceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import 'package:appmetrica_plugin/appmetrica_plugin.dart'; final deviceId = await AppMetrica.deviceId; if (deviceId != null) { try { - final builder = AdaptyProfileParametersBuilder() - ..setAppmetricaDeviceId(deviceId) - ..setAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceId, + ); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID", + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` ### AppsFlyer Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import 'package:appsflyer_sdk/appsflyer_sdk.dart'; AppsflyerSdk appsflyerSdk = AppsflyerSdk(); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); - await Adapty().updateAttribution( - data, - source: AdaptyAttributionSource.appsflyer, - networkUserId: appsFlyerUID, - ); + await Adapty().setIntegrationIdentifier( + key: "appsflyer_id", + value: appsFlyerUID, + ); + + await Adapty().updateAttribution(data, source: "appsflyer"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` ### Branch Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers FlutterBranchSdk.initSession().listen((data) async { try { + await Adapty().setIntegrationIdentifier( + key: "branch_id", + value: , + ); - await Adapty().updateAttribution(data, source: AdaptyAttributionSource.branch); + await Adapty().updateAttribution(data, source: "branch"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ); ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; try { - final builder = AdaptyProfileParametersBuilder() - ..setFirebaseAppInstanceId(appInstanceId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [настройка SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setMixpanelUserId(distinctId); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "mixpanel_user_id", + value: distinctId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers OneSignal.shared.setSubscriptionObserver((changes) { final playerId = changes.to.userId; if (playerId != null) { - final builder = - AdaptyProfileParametersBuilder() - ..setOneSignalPlayerId(playerId); - // ..setOneSignalSubscriptionId(playerId); try { - Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId, + ); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle error } } }); ``` ### Pushwoosh Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; - final builder = AdaptyProfileParametersBuilder() - ..setPushwooshHWID(hwid); try { - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: hwid, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Обновите реализацию режима Observer \{#update-observer-mode-implementation\} Обновите способ привязки пейволов к транзакциям. Раньше для назначения `variationId` использовался метод `setVariationId`. Теперь можно передавать `variationId` напрямую при записи транзакции с помощью нового метода `reportTransaction`. Итоговый пример кода приведён в разделе [Привязка пейволов к транзакциям покупок в режиме Observer](report-transactions-observer-mode-flutter). :::warning Не забудьте зафиксировать транзакцию с помощью метода `reportTransaction`. Если пропустить этот шаг, Adapty не распознает транзакцию, не предоставит уровни доступа, не включит её в аналитику и не передаст в интеграции. Этот шаг обязателен! ::: ```diff showLineNumbers try { - await Adapty().setVariationId("YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID"); + // every time when calling transaction.finish() + await Adapty().reportTransaction( + "YOUR_TRANSACTION_ID", + variationId: "PAYWALL_VARIATION_ID", // optional + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter-sdk-v3 --- --- title: "Миграция Adapty Flutter SDK на v3.0" description: "Мигрируйте на Adapty Flutter SDK v3.0 для улучшенной производительности и новых функций монетизации." --- Adapty SDK v3.0 добавляет поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Благодаря максимальной гибкости и широким возможностям дизайна ваши пейволы станут ещё эффективнее и прибыльнее. :::info Обратите внимание: библиотека AdaptyUI устарела и теперь включена в состав AdaptySDK. ::: ## Удаление AdaptyUI SDK \{#remove-adaptyi-sdk\} 1. AdaptyUI теперь является модулем Adapty SDK, поэтому удалите `adapty_ui_flutter` из файла `pubspec.yaml`: ```diff showLineNumbers dependencies: + adapty_flutter: ^3.2.1 - adapty_flutter: ^2.10.3 - adapty_ui_flutter: ^2.1.3 ``` 2. Выполните команду: ```bash showLineNumbers title="Bash" flutter pub get ``` ## Настройка Adapty SDK \{#configure-adapty-sdks\} Ранее для настройки Adapty SDK требовалось использовать файлы `Adapty-Info.plist` и `AndroidManifest.xml`. Теперь дополнительные файлы не нужны — все необходимые параметры передаются при активации. Настройку Adapty SDK достаточно выполнить один раз, как правило, при запуске приложения. ### Активация модуля Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} 1. Удалите импорт AdaptyUI SDK из вашего приложения: ```diff showLineNumbers import 'package:adapty_flutter/adapty_flutter.dart'; - import 'package:adapty_ui_flutter/adapty_ui_flutter.dart'; ``` 2. Обновите активацию Adapty SDK следующим образом: ```diff showLineNumbers try { - Adapty().activate(); + await Adapty().activate( + configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') + ..withLogLevel(AdaptyLogLevel.debug) + ..withObserverMode(false) + ..withCustomerUserId(null) + ..withIpAddressCollectionDisabled(false) + ..withIdfaCollectionDisabled(false), + ); } catch (e) { // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | | ----------------------------------- | -------------- | ------------------------------------------------------------ | | **PUBLIC_SDK_KEY** | обязательный | Ключ, который можно найти в поле **Public SDK key** в настройках приложения в Adapty: [**App settings**-> **General** tab -> **API keys** subsection](https://app.adapty.io/settings/general) | | **withLogLevel** | опциональный | Adapty записывает ошибки и другую важную информацию для анализа работы вашего приложения. Доступны следующие уровни логирования:
  • error: регистрируются только ошибки.
  • warn: регистрируются ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания.
  • info: регистрируются ошибки, предупреждения и важные информационные сообщения, например о жизненном цикле различных модулей.
  • verbose: регистрируется любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д.
| | **withObserverMode** | опциональный |

Булево значение, управляющее [режимом Observer](observer-vs-full-mode). Включите его, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики.

Значение по умолчанию: `false`.

🚧 В режиме Observer Adapty SDK не закрывает транзакции — убедитесь, что вы обрабатываете их самостоятельно.

| | **withCustomerUserId** | опциональный | Идентификатор пользователя в вашей системе. Мы передаём его в событиях подписки и аналитики, чтобы привязать события к нужному профилю. Вы также можете искать пользователей по `customerUserId` в меню [**Profiles and Segments**](https://app.adapty.io/profiles/users). | | **withIdfaCollectionDisabled** | опциональный |

Установите значение `true`, чтобы отключить сбор и передачу IDFA.

а также передачу IP-адреса пользователя.

Значение по умолчанию: `false`.

Подробнее о сборе IDFA см. в разделе [Интеграция с аналитикой](analytics-integration#disable-collection-of-advertising-identifiers).

| | **withIpAddressCollectionDisabled** | опциональный |

Установите значение `true`, чтобы отключить сбор и передачу IP-адреса пользователя.

Значение по умолчанию: `false`.

| ### Активация модуля AdaptyUI в SDK Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Настраивать модуль AdaptyUI нужно только в том случае, если вы планируете использовать [Paywall Builder](adapty-paywall-builder): ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer: , ); } catch (e) { // handle the error } ``` Обратите внимание, что конфигурация AdaptyUI необязательна — модуль AdaptyUI можно активировать без неё. Однако если вы используете конфигурацию, все её параметры обязательны. Параметры: | Параметр | Наличие | Описание | | :------------------------------ | :------- | :----------------------------------------------------------- | | **memoryStorageTotalCostLimit** | обязательный | Общий лимит стоимости хранилища в байтах. | | **memoryStorageCountLimit** | обязательный | Лимит количества элементов в памяти. | | **diskStorageSizeLimit** | обязательный | Лимит размера файла на диске в байтах. 0 означает отсутствие ограничений. | --- # End of Documentation _Generated on: 2026-07-24T13:01:12.756Z_ _Successfully processed: 44/44 files_