在 Flutter SDK 中首次启动时展示 AA 定向付费墙

本文适用于你的应用的 iOS 构建版本。Apple Ads 归因仅在 iOS 上可用。

Apple Ads (AA) 归因数据在 Adapty().activate() 之后异步到达。首次启动时通常尚未获取到归因数据,因此如果立即调用 getFlow,Adapty 会根据默认目标受众解析请求,导致 Apple Ads 用户错过你针对 AA 细分配置的付费墙。与其先展示付费墙再替换它,不如在显示任何内容之前短暂等待 AA 归因数据到达:如果在超时时间内获取到归因数据,则展示针对性付费墙;否则展示面向默认目标受众的付费墙。AdaptyProfile.appliedExternalAttributionProviders 可告知你 AA 归因数据何时已生效。

Important

此属性仅报告 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

首次启动时,你需要处理以下两种情况:

  1. 归因在超时时间内到达。 调用 getFlow — Adapty 将根据 Apple Ads 目标受众解析请求,并返回对应的付费墙。
  2. 超时时间先到期。 转而展示默认目标受众的付费墙,避免没有 Apple Ads 归因的用户一直等待。getFlowForDefaultAudience 无需等待细分结果即可返回。

appliedExternalAttributionProviders 可以为空。这意味着以下情况之一:

  • Apple Ads 归因尚未为该用户画像处理完毕。
  • 归因数据完全未到达。
  • 归因数据来自其他渠道,该数组不报告此类来源。

以上三种情况下,调用 getFlowForDefaultAudience 均是安全的——无论用户画像处于何种状态,它都会返回默认目标受众的付费墙。

Important

此等待仅适用于首次启动。一旦 Apple Ads 归因数据记录完成,它将永久存储在用户画像中。此后每次启动时,缓存的用户画像已在 appliedExternalAttributionProviders 中包含 AdaptyExternalAttributionProvider.appleAds,因此归因路径会立即解析,getFlow 也会直接返回基于 Apple Ads 市场细分的付费墙,无需任何等待。

实现

首次启动时,等待 AdaptyExternalAttributionProvider.appleAds,并设置一个硬超时——如果 Apple Ads 归因数据始终未到达,这些用户仍然需要看到付费墙。

  1. 激活 SDK。 请参阅安装并配置 Flutter SDK
  2. 通过 Adapty().didUpdateProfileStream.listen(…) 订阅用户画像更新。 如果尚未设置监听器,请参阅监听订阅更新
  3. 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() 单独检查当前用户画像。应用重启后,已存储的归因数据不会再次触发事件。

  1. 同时启动一个 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 是一个广播流,支持多个独立监听器互不干扰。