在 Flutter SDK 中通过 Flow 和付费墙编辑工具启用购买功能
要启用应用内购买,你需要了解三个核心概念:
- 产品 – 用户可购买的任何内容(订阅、消耗型商品、永久授权)
- 流程与付费墙 – 向用户展示产品的页面,通过无代码的流程与付费墙编辑工具构建,SDK 通过
getFlow获取它们。如果你更倾向于用自己的代码构建 UI,请参阅实现自定义付费墙。 - 版位 – 在应用中展示流程与付费墙的位置和时机(例如
main、onboarding、settings)。你可以在看板中将它们关联到版位,然后在代码中通过版位 ID 来请求。这样可以轻松进行 A/B 测试,并向不同用户展示不同内容。
Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种:
| 实现方式 | 复杂度 | 适用场景 |
|---|---|---|
| Adapty Flow 与付费墙编辑工具 | ✅ 简单 | 你在无代码编辑工具中创建完整的、可直接购买的流程或付费墙。Adapty 自动渲染界面,并在后台处理所有复杂的购买逻辑、收据验证和订阅管理。 |
| 自定义付费墙 | 🟡 中等 | 你在应用代码中自行实现付费墙 UI,但仍从 Adapty 获取流程对象,以保持产品方案的灵活性。请参阅指南。 |
| 观察者模式 | 🔴 复杂 | 你已有自己的购买处理基础设施,并希望继续使用它。请注意,观察者模式在 Adapty 中存在一定限制。请参阅文章。 |
以下步骤介绍如何在应用中展示通过 Adapty 流程与付费墙编辑工具创建的流程或付费墙。
如果您希望自行构建付费墙 UI,请参阅实现自定义付费墙。
要展示通过 Adapty 流程与付费墙编辑工具创建的流程或付费墙,只需在应用代码中完成以下操作:
- 获取流程或付费墙:从 Adapty 获取。
- 展示内容,Adapty 将为您处理购买流程:在应用中显示视图。
- 处理按钮操作:将用户交互与应用的响应关联起来。例如,当用户点击按钮时打开链接或关闭付费墙。
开始之前
在开始之前,请完成以下步骤:
- 在 Adapty 看板中将您的应用连接到 App Store 和/或 Google Play。
- 在 Adapty 中创建产品。
- 创建流程或付费墙并向其添加产品。
- 创建版位并将您的流程或付费墙添加到其中。
- 在您的应用代码中安装并激活 Adapty SDK。本指南使用 Adapty Flutter SDK v4 API。
完成这些步骤最快的方式是参照快速入门指南,或使用 Developer CLI 创建付费墙和版位。
1. 获取流程或付费墙
你的流程和付费墙与在看板中配置的版位相关联。通过版位,你可以针对不同的目标受众展示不同的流程或付费墙,也可以运行 A/B 测试。
要获取在 Adapty Flow & Paywall Builder 中创建的流程或付费墙,你需要:
-
通过
getFlow方法,使用版位 ID 获取flow对象,并通过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. 展示流程或付费墙
现在,当你获得流程视图后,只需添加几行代码即可将其展示出来。
要展示流程,请对由 createFlowView 方法创建的 view 调用 view.present() 方法。每个 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 — 缺少这些方法,代码将无法编译。
将观察者实现为一个专用的长生命周期对象,而不是 widget。由于整个应用只有一个全局观察者插槽,将其绑定到 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'),
),
),
);
}
}