Включение покупок с помощью Flow и Paywall Builder в Flutter SDK
Чтобы подключить встроенные покупки, нужно разобраться в трёх ключевых понятиях:
- Продукты – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ)
- Флоу – последовательности экранов, которые показывают пользователям продукты; создаются в визуальном редакторе Flow & Paywall Builder. SDK получает их через
getFlow. Если вы предпочитаете строить UI самостоятельно в коде, используйте пейволы — см. Реализация пейволов вручную. - Плейсменты – где и когда в приложении показываются флоу (например,
main,onboarding,settings). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает запуск A/B-тестов и показ разных флоу разным пользователям.
Adapty предлагает три способа подключить покупки в приложении. Выберите подходящий в зависимости от требований вашего приложения:
| Реализация | Сложность | Когда использовать |
|---|---|---|
| Adapty Flow & Paywall Builder | ✅ Просто | Вы создаёте полноценный, готовый к покупкам флоу в no-code конструкторе. Adapty автоматически отрисовывает его и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. |
| Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но по-прежнему получаете объект флоу из Adapty, сохраняя гибкость в настройке продуктов. См. гайд. |
| Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура для обработки покупок, и вы хотите продолжать её использовать. Учтите, что observer mode имеет ограничения в Adapty. См. статью. |
Ниже описано, как реализовать флоу, созданный в Adapty Flow & Paywall Builder.
Если вы предпочитаете создавать UI пейвола самостоятельно, см. Реализация пейволов вручную.
Чтобы отобразить флоу, созданный в Adapty Flow & Paywall Builder, в коде приложения нужно лишь:
- Получите флоу: Получите его из Adapty.
- Отобразите его — Adapty сам обработает покупки: Покажите представление в вашем приложении.
- Обрабатывайте действия кнопок: Свяжите действия пользователя с реакцией вашего приложения на них. Например, открывайте ссылки или закрывайте флоу при нажатии кнопок.
Прежде чем начать
Прежде чем начать, выполните следующие шаги:
- Подключите приложение к App Store и/или Google Play в дашборде Adapty.
- Создайте продукты в Adapty.
- Создайте флоу и добавьте в него продукты.
- Создайте плейсмент и добавьте в него флоу.
- Установите и активируйте SDK в коде приложения. В этом гайде используются API Adapty Flutter SDK v4.
Самый быстрый способ выполнить эти шаги — воспользоваться гайдом по быстрому старту или создать пейволы и плейсменты с помощью Developer CLI.
1. Получите флоу
Флоу связаны с плейсментами, настроенными в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать A/B-тесты.
Чтобы получить флоу, созданный в Adapty Flow & Paywall Builder, нужно:
-
Получить объект
flowпо ID плейсмента с помощью методаgetFlowи проверить, создан ли он в билдере — это определяет свойствоhasViewConfiguration. -
Создать представление флоу с помощью метода
createFlowView. Представление содержит элементы UI и стили, необходимые для отображения флоу.
Чтобы получить конфигурацию вью, опубликуйте флоу. Флоу с неопубликованными изменениями имеет статус Dirty, и его плейсмент продолжает отдавать последнюю опубликованную версию.
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. Отобразите флоу
Теперь, когда у вас есть объект флоу, достаточно добавить несколько строк, чтобы его отобразить.
Для отображения флоу вызовите метод view.present() на объекте view, созданном методом createFlowView. Каждый view можно показать только один раз: после закрытия он освобождается из памяти. Если нужно показать флоу снова, вызовите createFlowView ещё раз, чтобы создать новый экземпляр view.
try {
await view.present();
} on AdaptyError catch (e) {
// handle the error
} catch (e) {
// handle the error
}
Подробнее о том, как отобразить флоу, читайте в нашем гайде.
3. Обработка действий кнопок
Когда пользователи нажимают кнопки во флоу, Flutter SDK автоматически обрабатывает покупки, восстановление, закрытие экрана и открытие URL. Однако у других кнопок есть пользовательские или предустановленные ID, и обработку таких действий нужно реализовать в вашем коде.
Чтобы управлять процессами на экране флоу или отслеживать их, реализуйте методы AdaptyUIFlowsEventsObserver и установите наблюдатель до показа любого экрана. Если пользователь выполнил какое-либо действие, будет вызван flowViewDidPerformAction, и ваше приложение должно отреагировать в зависимости от ID действия.
Три метода наблюдателя обязательны: flowViewDidFinishPurchase, flowViewDidFinishRestore и flowViewDidReceiveError — без них класс не скомпилируется.
Реализуйте наблюдатель как отдельный долгоживущий объект, а не виджет. Поскольку во всём приложении используется единственный глобальный слот для наблюдателя, привязка его к State приведёт к утечке экрана (SDK хранит на него сильную ссылку) и молчаливой замене при регистрации следующего экрана. Использование extends также наследует поведение SDK по умолчанию, поэтому помимо трёх обязательных методов достаточно переопределить только нужные коллбэки.
// 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();
}
}
Зарегистрируйте обработчик один раз при запуске приложения, до отображения любого флоу:
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
Следующие шаги
Есть вопросы или возникли проблемы? Загляните на наш форум поддержки, где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь!
Ваш пейвол готов к отображению в приложении. Протестируйте покупки в песочнице App Store или Google Play Store, чтобы убедиться, что тестовая покупка через пейвол проходит успешно.
Теперь нужно проверить уровень доступа пользователей, чтобы показывать пейвол или открывать доступ к платным функциям только нужным пользователям.
Полный пример
Вот как все эти шаги можно объединить в вашем приложении.
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<FlowScreen> createState() => _FlowScreenState();
}
class _FlowScreenState extends State<FlowScreen> {
@override
void initState() {
super.initState();
_showFlowIfNeeded();
}
Future<void> _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'),
),
),
);
}
}