---
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<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'),
        ),
      ),
    );
  }
}
```