Adapty Paywall 已上线但 Flutter 无法显示 UI
结论
getPaywall() 成功,说明 Adapty 已根据 Placement 返回 Paywall 及其 variation,但这个 variation 不一定包含 AdaptyUI 可以渲染的 Paywall Builder 配置。
日志中的关键信号是:
hasViewConfiguration: false
GET /paywall-builder/ ... statusCode: 404
这表示当前 variation 没有可用的 UI 配置,所以 AdaptyUI().createPaywallView() 无法创建 View。
Dashboard 中的 live 状态通常只表示 Paywall 或 Placement 已发布,SDK 可以获取它。还需要单独确认当前 variation 对应的 Paywall Builder 页面已经创建、保存并发布。
remoteConfig: null 不一定表示异常。Remote Config 和 Paywall Builder UI 配置是两套不同的数据。没有配置 Remote Config 时,返回 null 是正常的。
常见原因
1. 只发布了 Paywall,没有发布可视化页面
Paywall 可以承载产品、权限和实验配置。即使没有在 Paywall Builder 中创建并发布页面,SDK 仍可能通过 getPaywall() 获取它,但此时:
paywall.hasViewConfiguration == false
这种 Paywall 适合由应用自行绘制购买界面,不能直接传给 AdaptyUI().createPaywallView()。
请进入 Dashboard,找到当前 Paywall 返回的具体 variation,确认它已绑定 Builder 页面,并且页面已经保存和发布。仅看到 Paywall 状态为 live 还不够。
2. Placement 返回了另一个 variation
如果 Placement 配置了 A/B 测试、受众规则或多个 variation,设备实际拿到的版本可能不是你正在 Dashboard 中检查的版本。
日志中的:
variationId: <<variationid>>
experimentName: Subscription
只能说明设备命中了某个 variation。需要根据 variationId 在 Dashboard 中找到实际返回的版本,再确认这个 variation 是否包含已发布的 Builder UI。不要只检查同一 Paywall 下的默认 variation。
3. App 使用了其他项目的 Public SDK Key
如果应用初始化时使用的 Public SDK Key 属于另一个 Adapty 项目,可能出现以下情况:
- Placement ID 在另一个项目中也存在;
- SDK 获取到了同名或旧的 Paywall;
- Builder UI 配置不属于当前 SDK Key 对应的项目。
检查 Adapty.activate() 使用的 Public SDK Key,确认它与当前编辑 Paywall 的 Dashboard 项目一致。不要混用测试项目、生产项目、iOS 项目或 Android 项目的 Key。
不要在日志、截图或客户端代码仓库中公开 Secret Key。移动端只能使用适用于客户端的 Public SDK Key。
4. Dashboard 修改尚未反映到当前结果
修改 Paywall、Placement 或实验后,设备可能仍在使用缓存的配置。可以按以下顺序处理:
- 确认 Builder 页面已经发布。
- 重新启动应用并请求 Paywall。
- 清除测试设备上的应用数据,或重新安装应用。
- 暂时关闭相关实验或受众规则,改用单一 variation 验证。
- 新建一个临时 Placement,并关联带有 Builder UI 的 Paywall,用它排除旧配置或缓存的影响。
缓存控制参数取决于项目使用的 Adapty Flutter SDK 版本,不要直接照搬其他版本的 API。
5. Flutter、Adapty 原生 SDK 与 AdaptyUI 版本不兼容
错误详情中包含原生组件版本:
[2.10.3]
如果项目使用的 Flutter SDK、原生 Adapty SDK 或 AdaptyUI 较旧,可能无法正确处理 Dashboard 当前生成的 Builder 配置。还需要检查 adapty_flutter 和对应 AdaptyUI 包的版本是否兼容。
升级时应参考当前安装版本对应的官方迁移文档,因为不同主版本的初始化方式、类型和方法签名可能不同。升级后重新安装 Flutter 依赖,并根据平台刷新 iOS Pods 或 Android 构建缓存。
不过,仅凭这段日志还不能确定是版本问题。hasViewConfiguration: false 更直接地说明当前 variation 没有返回 UI 配置。
推荐排查顺序
第一步:确认拿到的是预期 variation
保留并核对以下信息:
debugPrint('placementId: $placementId');
debugPrint('variationId: ${paywall.variationId}');
debugPrint('paywallName: ${paywall.name}');
debugPrint('hasViewConfiguration: ${paywall.hasViewConfiguration}');
在 Dashboard 中找到这个准确的 variationId,确认它不是实验中的另一个 variation。
第二步:检查该 variation 的 Builder 页面
逐项确认:
- 页面确实通过 Adapty Paywall Builder 创建;
- 页面绑定到了当前返回的 variation;
- Builder 中的修改已经保存并发布;
- 页面不只是草稿;
- Placement 当前指向的确实是该 Paywall 或 variation;
- 产品配置完整,没有引用已删除或未绑定的商品。
需要分别确认 Paywall 已经 live,以及 Builder UI 已经发布。
第三步:确认项目和 SDK Key
检查应用初始化代码,例如:
await Adapty().activate(
configuration: AdaptyConfiguration(
apiKey: publicSdkKey,
),
);
具体签名以项目当前使用的 SDK 版本为准。确认 publicSdkKey 来自正在编辑该 Paywall 的同一个 Dashboard 项目。
第四步:升级兼容的 SDK 组合
检查 pubspec.yaml、pubspec.lock,以及 iOS 和 Android 最终解析出的版本。升级时需要保持核心 SDK 与 AdaptyUI 版本兼容,不要只升级其中一个。
如果升级后仍然返回相同的 404,应继续检查 Dashboard 配置、项目归属和 variation 绑定,而不是反复修改 locale。
第五步:用最小配置交叉验证
创建一个新的测试 Placement:
- 只关联一个 Paywall;
- 不启用 A/B 测试和复杂受众规则;
- 使用 Builder 创建一个最简单的页面;
- 保存并发布;
- 在同一设备上使用该 Placement ID 发起请求。
如果新 Placement 可以正常显示,说明 SDK 集成本身基本正常,问题更可能出在原 Placement 的 variation、实验配置或 Builder 发布状态。
代码中应先检查 UI 配置
创建 View 前先判断 hasViewConfiguration,可以避免继续把没有 Builder UI 的 Paywall 传给 AdaptyUI:
static Future<void> showPaywallUI({
required String placementId,
String? locale,
}) async {
try {
final paywall = await getPaywallByPlacement(placementId);
if (paywall == null) {
debugPrint('❌ Paywall not found for placement: $placementId');
return;
}
debugPrint('✅ Paywall loaded for placement: $placementId');
debugPrint('🧩 variationId: ${paywall.variationId}');
debugPrint(
'📦 hasViewConfiguration: ${paywall.hasViewConfiguration}',
);
debugPrint('🧾 remoteConfig: ${paywall.remoteConfig}');
debugPrint('🧮 paywallName: ${paywall.name}');
if (!paywall.hasViewConfiguration) {
debugPrint(
'❌ The returned paywall variation has no published '
'Paywall Builder configuration.',
);
return;
}
final view = await AdaptyUI().createPaywallView(
paywall: paywall,
locale: locale ?? 'en',
);
await view.present();
debugPrint('✅ Paywall UI presented for: $placementId');
} catch (e, stackTrace) {
debugPrint('❌ Failed to present Paywall UI: $e');
debugPrintStack(stackTrace: stackTrace);
}
}
这项检查只能让配置问题更容易被发现,不能自动补上缺失的 UI。如果该 Paywall 原本就用于自定义界面,应使用 getPaywall() 返回的产品数据自行绘制 UI,而不是调用 createPaywallView()。
关于 locale
方法接收了 locale 参数,但原实现固定传入:
locale: 'en'
可以改为:
locale: locale ?? 'en'
不过,缺少某种本地化版本通常不能直接解释当前的 hasViewConfiguration: false 和 Builder 接口 404。应先检查基础 Builder 配置是否存在并已发布。
仍无法解决时需要提供的信息
如果已经确认 Builder UI 发布到了正确的 variation,但请求仍然返回 404,可以向 Adapty 支持提供:
- Adapty 项目 ID;
- Placement ID;
- 日志中的
variationId; - Paywall ID;
adapty_flutter和 AdaptyUI 包版本;- iOS 或 Android 平台及系统版本;
- 完整错误码、HTTP 状态码和发生时间;
- Dashboard 中 variation 与 Builder 发布状态的截图。
不要公开完整 SDK Key。如果此类 404 并非由配置错误或项目不匹配引起,需要由 Adapty 根据项目和 Builder 资源状态检查服务端记录。
备注:内容仅供参考。
