Включение покупок с помощью 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, сохраняя гибкость в управлении продуктами. См. гайд.
Режим наблюдателя🔴 СложноУ вас уже есть собственная инфраструктура для обработки покупок, и вы хотите её сохранить. Обратите внимание, что режим наблюдателя имеет ограничения в Adapty. См. статью.
Important

Шаги ниже показывают, как реализовать флоу или пейвол, созданный в Adapty Flow & Paywall Builder.

Если вы предпочитаете создать UI пейвола самостоятельно, см. Реализация кастомных пейволов.

Чтобы отобразить флоу или пейвол, созданный в Adapty Flow & Paywall Builder, в коде приложения нужно только:

  1. Получите флоу или пейвол: Запросите его из Adapty.
  2. Отобразите его — Adapty возьмёт на себя покупки: Покажите экран в приложении.
  3. Обрабатывайте действия кнопок: Свяжите действия пользователя с реакцией приложения. Например, открывайте ссылки или закрывайте пейвол при нажатии кнопок.

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

Прежде чем начать, выполните следующие шаги:

  1. Подключите ваше приложение к App Store и/или Google Play в дашборде Adapty.
  2. Создайте продукты в Adapty.
  3. Создайте флоу или пейвол и добавьте в него продукты.
  4. Создайте плейсмент и добавьте в него флоу или пейвол.
  5. Установите и активируйте SDK в коде вашего приложения. В этом гайде используются API Adapty Flutter SDK v4.
Tip

Самый быстрый способ выполнить эти шаги — воспользоваться гайдом по быстрому старту или создать пейволы и плейсменты с помощью Developer CLI.

1. Получите флоу или пейвол

Ваши флоу и пейволы связаны с плейсментами, настроенными в дашборде. Плейсменты позволяют показывать разные флоу или пейволы для разных аудиторий или запускать A/B-тесты.

Чтобы получить флоу или пейвол, созданный в Adapty Flow & Paywall Builder, нужно:

  1. Получить объект flow по ID плейсмента с помощью метода getFlow и проверить, был ли он создан в конструкторе, через свойство hasViewConfiguration.

  2. Создайте представление флоу с помощью метода createFlowView. Представление содержит элементы интерфейса и стили, необходимые для его отображения.

Important

Чтобы получить конфигурацию представления, опубликуйте флоу или пейвол. Флоу или пейвол с неопубликованными изменениями имеет статус 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 флоу, достаточно добавить несколько строк, чтобы его отобразить.

Для отображения флоу используйте метод view.present() на объекте view, созданном методом createFlowView. Каждый view можно отобразить только один раз: после закрытия view освобождается из памяти. Если нужно снова отобразить флоу, вызовите createFlowView ещё раз, чтобы создать новый экземпляр view.

try {
  await view.present();
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
Tip

Подробнее о том, как отображать флоу или пейвол, читайте в нашем гайде.

3. Обработка действий кнопок

Когда пользователи нажимают кнопки во флоу или пейволе, Flutter SDK автоматически обрабатывает покупки, восстановление, закрытие экрана и открытие URL. Однако другие кнопки имеют пользовательские или предустановленные ID и требуют обработки действий в вашем коде.

Чтобы управлять процессами на экране флоу или отслеживать их, реализуйте методы AdaptyUIFlowsEventsObserver и установите обозреватель до отображения любого экрана. Если пользователь выполнил какое-либо действие, будет вызван flowViewDidPerformAction, и ваше приложение должно отреагировать в зависимости от ID действия.

Три метода наблюдателя обязательны: flowViewDidFinishPurchase, flowViewDidFinishRestore и flowViewDidReceiveError — без них класс не скомпилируется.

Tip

Читайте наши гайды по обработке действий и событий кнопок.

Реализуйте наблюдатель как отдельный долгоживущий объект, а не виджет. Поскольку во всём приложении используется единственный глобальный слот для наблюдателя, привязка его к 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());

Дальнейшие шаги

Tip

Есть вопросы или возникли проблемы? Загляните на наш форум поддержки, где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь!

Ваш флоу или пейвол готов к отображению в приложении. Протестируйте покупки в песочнице 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'),
        ),
      ),
    );
  }
}