在 Flutter SDK 中首次启动时展示 AA 定向付费墙
本文适用于你的应用的 iOS 构建版本。Apple Ads 归因仅在 iOS 上可用。
Apple Ads (AA) 归因数据在 Adapty().activate() 之后异步到达。首次启动时通常尚未获取到归因数据,因此如果立即调用 getFlow,Adapty 会根据默认目标受众解析请求,导致 Apple Ads 用户错过你针对 AA 细分配置的付费墙。与其先展示付费墙再替换它,不如在显示任何内容之前短暂等待 AA 归因数据到达:如果在超时时间内获取到归因数据,则展示针对性付费墙;否则展示面向默认目标受众的付费墙。AdaptyProfile.appliedExternalAttributionProviders 可告知你 AA 归因数据何时已生效。
此属性仅报告 Apple Ads 的数据。来自其他提供商的归因信息可在用户画像中查看,也可用于市场细分筛选,但暂不在此处显示。
开始之前
你需要:
- Adapty Flutter SDK 4.1 或更高版本。在 4.0.x 版本中,用户画像属性名为
appliedAttributionSources,其值类型为AdaptyAttributionSource——请参阅 迁移到 v4.1。在 3.17.0–3.x 版本中,流程同样通过getPaywall/getPaywallForDefaultAudience获取,返回类型为AdaptyPaywall——请参阅 迁移到 v4.0。 - 在 Adapty 中为应用配置 Apple Ads。请参阅 Apple Ads。
工作原理
调用 Adapty().activate() 后,SDK 会在后台向 Apple 请求 Apple Ads 归因数据,并将结果转发至 Adapty 后端。当 AA 成为该用户画像的有效归因来源时,SDK 会通过 didUpdateProfileStream 监听器推送更新后的 AdaptyProfile,其中 appliedExternalAttributionProviders 列表里将包含 AdaptyExternalAttributionProvider.appleAds。
首次启动时,你需要处理以下两种情况:
- 归因在超时时间内到达。 调用
getFlow— Adapty 将根据 Apple Ads 目标受众解析请求,并返回对应的付费墙。 - 超时时间先到期。 转而展示默认目标受众的付费墙,避免没有 Apple Ads 归因的用户一直等待。
getFlowForDefaultAudience无需等待细分结果即可返回。
appliedExternalAttributionProviders 可以为空。这意味着以下情况之一:
- Apple Ads 归因尚未为该用户画像处理完毕。
- 归因数据完全未到达。
- 归因数据来自其他渠道,该数组不报告此类来源。
以上三种情况下,调用 getFlowForDefaultAudience 均是安全的——无论用户画像处于何种状态,它都会返回默认目标受众的付费墙。
此等待仅适用于首次启动。一旦 Apple Ads 归因数据记录完成,它将永久存储在用户画像中。此后每次启动时,缓存的用户画像已在 appliedExternalAttributionProviders 中包含 AdaptyExternalAttributionProvider.appleAds,因此归因路径会立即解析,getFlow 也会直接返回基于 Apple Ads 市场细分的付费墙,无需任何等待。
实现
首次启动时,等待 AdaptyExternalAttributionProvider.appleAds,并设置一个硬超时——如果 Apple Ads 归因数据始终未到达,这些用户仍然需要看到付费墙。
- 激活 SDK。 请参阅安装并配置 Flutter SDK。
- 通过
Adapty().didUpdateProfileStream.listen(…)订阅用户画像更新。 如果尚未设置监听器,请参阅监听订阅更新。 - 在
appliedExternalAttributionProviders中监测AdaptyExternalAttributionProvider.appleAds。 当它出现时,使用getFlow加载付费墙——Adapty 将返回经过 AA 细分的实验变体:
final subscription = Adapty().didUpdateProfileStream.listen((profile) async {
if (!profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.appleAds)) return;
final paywall = await Adapty().getFlow(placementId: placementId);
// present the segmented paywall, then cancel the subscription and the timer
});
didUpdateProfileStream 是广播流,不会重播历史事件,因此还需通过 getProfile() 单独检查当前用户画像。应用重启后,已存储的归因数据不会再次触发事件。
- 同时启动一个 3–5 秒的计时器与订阅。 若计时器在
AdaptyExternalAttributionProvider.appleAds出现之前触发,则改用getFlowForDefaultAudience加载默认受众的付费墙。优先展示最先返回的付费墙,并取消另一条路径,避免重复拉取。同时为该版位配置备用付费墙,确保在网络请求失败时用户不会陷入等待。
完整示例
下面的实现将归因获取与超时进行竞速,同时预取默认目标受众付费墙,并返回合适的付费墙。调用方只需 await 一个函数——无需在调用处管理监听器或状态标志:
- 如果归因在
timeout时间内到达,则通过getFlow返回细分后的付费墙。 - 如果
timeout先到期,则通过getFlowForDefaultAudience返回预取的默认目标受众付费墙。
/// Returns the Apple Ads-segmented paywall if attribution is applied within
/// [timeout], otherwise the default-audience paywall. Call after Adapty().activate().
Future<AdaptyFlow> getFlowOrDefault({
required String placementId,
required Duration timeout,
}) {
// Prefetch the default-audience paywall right away so the timeout path resolves
// without an extra network round-trip. `getFlowForDefaultAudience` skips the
// wait for segmentation data. `..ignore()` keeps an unused prefetch from surfacing
// as an unhandled error; the error still reaches the caller if this paywall wins.
final defaultPaywall =
Adapty().getFlowForDefaultAudience(placementId: placementId)..ignore();
final completer = Completer<AdaptyFlow>();
late final StreamSubscription<AdaptyProfile> subscription;
late final Timer timer;
void resolve(Future<AdaptyFlow> paywall) {
if (completer.isCompleted) return;
timer.cancel();
subscription.cancel();
completer.complete(paywall);
}
void onProfile(AdaptyProfile profile) {
if (profile.appliedExternalAttributionProviders.contains(AdaptyExternalAttributionProvider.appleAds)) {
resolve(Adapty().getFlow(placementId: placementId));
}
}
// Attribution path: react to profile updates as attribution is applied.
subscription = Adapty().didUpdateProfileStream.listen(onProfile);
// The stream is a broadcast stream and doesn't replay, so check the current
// profile too — on relaunches attribution is already stored and won't re-emit.
Adapty().getProfile().then(onProfile).ignore();
// Timeout path: fall back to the prefetched default-audience paywall.
timer = Timer(timeout, () => resolve(defaultPaywall));
return completer.future;
}
在启动画面中调用,待其完成后再展示付费墙:
try {
final paywall = await getFlowOrDefault(
placementId: 'YOUR_PLACEMENT_ID',
timeout: const Duration(seconds: 5),
);
// present the paywall
} on AdaptyError catch (adaptyError) {
// handle the error or show a fallback paywall
} catch (e) {
// handle the error
}
调整 timeout 参数,设置用户在付费墙出现前最多等待的时间。大多数用户没有 Apple Ads 归因数据,因此他们会等待完整的超时时长——3 到 5 秒是一个合理的平衡点。如果有归因数据,通常会在应用启动后几秒内到达。
如果你的应用已经在监听 didUpdateProfileStream(例如用于检查订阅状态),则无需做任何修改。didUpdateProfileStream 是一个广播流,支持多个独立监听器互不干扰。