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