Показ пейвола с таргетингом Apple Ads при первом запуске в Flutter SDK
Эта статья относится к 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. - Apple Ads, настроенный для приложения в Adapty. См. Apple Ads.
Как это работает
После вызова Adapty().activate() SDK в фоновом режиме запрашивает атрибуцию Apple Ads у Apple и передаёт результат в бэкенд Adapty. Когда AA становится активным источником атрибуции для профиля, SDK доставляет обновлённый AdaptyProfile в слушатель didUpdateProfileStream, где в списке appliedExternalAttributionProviders появляется AdaptyExternalAttributionProvider.appleAds.
При первом запуске возможны два сценария:
- Атрибуция поступает до истечения таймаута. Вызовите
getFlow— Adapty обрабатывает запрос с учётом аудитории Apple Ads и возвращает целевой пейвол. - Таймаут истекает первым. В этом случае покажите пейвол из аудитории по умолчанию, чтобы пользователи без атрибуции Apple Ads не ждали.
getFlowForDefaultAudienceвернёт его без ожидания сегментации.
appliedExternalAttributionProviders может быть пустым. Это означает одно из следующего:
- Атрибуция Apple Ads ещё не обработана для этого профиля.
- Атрибуция вообще не поступала.
- Атрибуция поступила от другого провайдера, который этот массив не отражает.
Во всех трёх случаях вызов getFlowForDefaultAudience безопасен — он возвращает пейвол для аудитории по умолчанию вне зависимости от состояния профиля.
Ожидание применяется только к первому запуску. После того как атрибуция Apple Ads сохранена, она постоянно хранится в профиле. При каждом последующем запуске кешированный профиль уже содержит AdaptyExternalAttributionProvider.appleAds в appliedExternalAttributionProviders, поэтому путь атрибуции разрешается мгновенно и getFlow возвращает пейвол с сегментацией по Apple Ads без какой-либо задержки.
Реализация
При первом запуске дождитесь AdaptyExternalAttributionProvider.appleAds и установите жёсткий таймаут — если атрибуция Apple Ads так и не поступит, эти пользователи всё равно должны увидеть пейвол.
- Активируйте SDK. См. Установка и настройка Flutter SDK.
- Подпишитесь на обновления профиля с помощью
Adapty().didUpdateProfileStream.listen(…). Если вы ещё не настроили слушатель, см. Отслеживание обновлений подписки. - Отслеживайте
AdaptyExternalAttributionProvider.appleAdsвappliedExternalAttributionProviders. Когда он появится, загрузите пейвол с помощьюgetFlow— Adapty вернёт вариант с сегментацией по Apple Ads:
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. Отображайте тот пейвол, который разрешится первым, и отменяйте второй путь, чтобы пейвол не запрашивался дважды. Настройте резервный пейвол для плейсмента, чтобы пользователь не оставался ни с чем при неудачном сетевом запросе.
Полный пример
Реализация ниже запускает гонку между атрибуцией и таймаутом, параллельно предзагружает пейвол для аудитории по умолчанию и возвращает подходящий пейвол. На стороне вызова достаточно дождаться одной функции — никаких слушателей или флагов состояния:
- Если атрибуция поступила в пределах
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 — это широковещательный поток (broadcast stream), поэтому он поддерживает несколько независимых слушателей, не мешая друг другу.