---
title: "在 Flutter SDK 中使用付费墙编辑工具启用购买功能"
description: "使用 Adapty 付费墙编辑工具启用应用内购买的快速入门指南。"
---

要启用应用内购买，你需要了解三个核心概念：
- [**产品**](product) – 用户可以购买的任何内容（订阅、消耗型商品、永久授权）
- [**流程**](adapty-flow-builder) – 向用户展示产品的界面序列，通过无代码 Flow Builder 构建。SDK 通过 `getFlow` 获取它们。如果你更倾向于在自己的代码中构建 UI，请使用付费墙代替 — 参见[手动实现付费墙](flutter-quickstart-manual)。
- [**版位**](placements) – 在应用中的何处、何时展示流程（如 `main`、`onboarding`、`settings`）。你在看板中将流程关联到版位，然后在代码中通过版位 ID 请求它们。这样可以轻松运行 A/B 测试，并向不同用户展示不同的流程。
Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种：
| 实现方式 | 复杂度 | 适用场景 |
|------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可直接购买的流程](quickstart-paywalls)。Adapty 会自动渲染，并在后台处理所有复杂的购买流程、收据验证和订阅管理。 |
| 手动创建付费墙 | 🟡 中等 | 您在应用代码中自行实现付费墙 UI，但仍从 Adapty 获取流程对象，以保持产品方案的灵活性。请参阅[指南](flutter-quickstart-manual)。 |
| Observer 模式 | 🔴 困难 | 您已有自己的购买处理基础设施，并希望继续使用。请注意，Observer 模式在 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. 在 Adapty 看板中将你的应用连接到 [App Store](initial_ios) 和/或 [Google Play](initial-android)。
2. 在 Adapty 中[创建产品](create-product)。
3. [创建流程并向其添加产品](create-paywall)。
4. [创建版位并将流程添加到其中](create-placement)。
5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-flutter)。本指南使用 Adapty Flutter SDK v4 API。
:::tip
完成这些步骤最快的方式是参照[快速入门指南](quickstart)，或使用 [Developer CLI](developer-cli-quickstart) 创建付费墙和版位。
:::
## 1. 获取流程 \{#1-get-the-flow\}

你的流程与在看板中配置的版位相关联。通过版位，你可以为不同的目标受众运行不同的流程，或进行 [A/B 测试](ab-tests)。

要获取在 Adapty 付费墙编辑工具中创建的流程，你需要：

1. 使用 `getFlow` 方法通过[版位](placements) ID 获取 `flow` 对象，并通过 `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\}

获取到 flow 视图后，只需添加几行代码即可将其展示出来。

要展示流程，请对由 `createFlowView` 方法创建的 `view` 调用 `view.present()` 方法。每个 `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. 处理按钮操作 \{#handle-button-actions\}

当用户点击流程中的按钮时，Flutter SDK 会自动处理购买、恢复购买、关闭视图和打开 URL 等操作。但其他按钮具有自定义或预定义的 ID，需要在代码中处理相应操作。

要控制或监控流程屏幕上的操作，请实现 `AdaptyUIFlowsEventsObserver` 方法，并在展示任何屏幕之前设置观察者。当用户执行某个操作时，`flowViewDidPerformAction` 会被触发，您的应用需要根据操作 ID 做出相应响应。
三个观察者方法是**必须实现的**：`flowViewDidFinishPurchase`、`flowViewDidFinishRestore` 和 `flowViewDidReceiveError` — 缺少这些方法，代码将无法编译。

:::tip
请参阅我们的指南，了解如何处理按钮[操作](flutter-handle-paywall-actions)和[事件](flutter-handling-events)。
:::
将观察者实现为一个专用的长生命周期对象，而不是 widget。由于整个应用只有一个全局观察者插槽，将其绑定到 `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'),
        ),
      ),
    );
  }
}
```