Отображение флоу и пейволов — React Native

Display flows and paywalls
Флоу Создаются в Flow Builder — рендерятся нативно на устройстве, без WebView
Пейволы Paywall Builder Весь существующий контент Paywall Builder

Если вы создали флоу или пейвол в Flow Builder, вам не нужно беспокоиться о том, как отрендерить его в коде мобильного приложения для отображения пользователю. Такой флоу содержит и то, что должно быть показано, и то, как именно это должно быть показано.

Прежде чем начать, убедитесь, что:

  1. Вы создали флоу или пейвол.
  2. Вы добавили его в плейсмент.
  3. Вы получили флоу и подготовили представление.

Этот гайд предназначен только для флоу и пейволов Paywall Builder, которые требуют SDK v4.0 или выше. Процесс отображения флоу отличается для пейволов на Remote Config.

Adapty React Native SDK предоставляет два способа отображения флоу и пейволов:

  • React-компонент: встраиваемый компонент, который можно интегрировать в архитектуру и систему навигации вашего приложения.

  • Модальное представление

React-компонент

Чтобы встроить флоу в существующее дерево компонентов, используйте компонент AdaptyFlowView напрямую в иерархии React Native-компонентов. Встроенный компонент позволяет интегрировать его в архитектуру приложения и систему навигации.

Компонент AdaptyFlowView создаёт своё представление в момент рендеринга — когда загружаются конфигурация и изображения. Чтобы предзагрузить их, вызовите createFlowView для того же флоу раньше в приложении. Тогда компонент использует кэшированные данные и рендерится без ожидания загрузки.


function MyFlow({ flow }) {
  const flowParams = useMemo(() => ({
    loadTimeoutMs: 3000,
  }), []);

  const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []);
  const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []);
  const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []);
  const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []);
  const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []);
  const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []);
  const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []);
  const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []);
  const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []);
  const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []);
  const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []);
  const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => {}, []);
  const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []);
  const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []);

  return (
    <AdaptyFlowView
      flow={flow}
      params={flowParams}
      style={styles.flow}
      onCloseButtonPress={onCloseButtonPress}
      onProductSelected={onProductSelected}
      onPurchaseStarted={onPurchaseStarted}
      onPurchaseCompleted={onPurchaseCompleted}
      onPurchaseFailed={onPurchaseFailed}
      onRestoreStarted={onRestoreStarted}
      onRestoreCompleted={onRestoreCompleted}
      onRestoreFailed={onRestoreFailed}
      onAppeared={onAppeared}
      onError={onError}
      onLoadingProductsFailed={onLoadingProductsFailed}
      onCustomAction={onCustomAction}
      onUrlPress={onUrlPress}
      onWebPaymentNavigationFinished={onWebPaymentNavigationFinished}
    />
  );
}

Чтобы показать флоу как отдельный экран, используйте метод view.present() на объекте view, созданном методом createFlowView. Каждый view можно использовать только один раз. Если нужно показать флоу повторно, вызовите createFlowView ещё раз, чтобы создать новый экземпляр view.

Повторное использование одного и того же view без его пересоздания запрещено. Это приведёт к ошибке AdaptyUIError.viewAlreadyPresented.


const view = await createFlowView(flow);

// Optional: handle flow events (close, purchase, restore, etc)
// view.setEventHandlers({ ... });

try {
  await view.present();
} catch (error) {
  // handle the error
}

Каждый повторный вызов setEventHandlers перезаписывает обработчики: заменяются как дефолтные, так и ранее установленные обработчики для указанных событий.

Настройка стиля отображения на iOS

Настройте способ отображения флоу на iOS, передав параметр iosPresentationStyle в метод present(). Параметр принимает значения 'full_screen' (по умолчанию) или 'page_sheet'.

try {
  await view.present({ iosPresentationStyle: 'page_sheet' });
} catch (error) {
  // handle the error
}

Использование таймеров, определённых разработчиком

Чтобы использовать таймеры, определённые разработчиком, в мобильном приложении, используйте timerId — в данном примере CUSTOM_TIMER_NY, Timer ID таймера, заданного в дашборде Adapty. Это обеспечивает динамическое обновление таймера с правильным значением — например, 13d 09h 03m 34s (вычисляется как время окончания таймера, например Новый год, минус текущее время).

В этом примере CUSTOM_TIMER_NY — это Timer ID таймера, заданного разработчиком в дашборде Adapty. timerResolver обеспечивает динамическое обновление таймера с нужным значением — например, 13d 09h 03m 34s (вычисляется как разница между временем окончания таймера, например Новым годом, и текущим временем).

Показ диалогов

Используйте этот метод вместо нативных диалогов предупреждений, когда на Android отображается флоу. На Android обычные RN-алерты появляются позади флоу и становятся невидимыми для пользователей. Этот метод обеспечивает корректное отображение диалога поверх флоу на всех платформах.

try {
  const action = await view.showDialog({
    title: 'Close paywall?',
    content: 'You will lose access to exclusive offers.',
    primaryActionTitle: 'Stay',
    secondaryActionTitle: 'Close',
  });

  if (action === 'secondary') {
    // User confirmed - close the flow
    await view.dismiss();
  }
  // If primary - do nothing, user stays
} catch (error) {
  // handle error
}

Замена одной подписки на другую

Когда пользователь пытается приобрести новую подписку, пока на Android активна другая, вы можете управлять тем, как должна обрабатываться новая покупка, — для этого передайте параметры обновления подписки при создании представления флоу. Чтобы заменить текущую подписку новой, используйте productPurchaseParams в createFlowView с параметрами oldSubVendorProductId и prorationMode.


const productPurchaseParams = flow.paywalls
  .flatMap((variation) => variation.productIdentifiers)
  .map((productId) => {
    let params = {};
    if (Platform.OS === 'android') {
      params.android = {
        subscriptionUpdateParams: {
          oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION',
          prorationMode: 'with_time_proration',
        },
      };
    }
    return { productId, params };
  });

const view = await createFlowView(flow, { productPurchaseParams });

Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения — всё уже задано внутри самого пейвола: и содержимое, и способ отображения.

Прежде чем начать, убедитесь, что:

  1. Вы создали пейвол.
  2. Вы добавили пейвол в плейсмент.
  3. Вы получили пейвол и подготовили представление.

Это руководство предназначено только для пейволов, созданных в новом Paywall Builder, которые требуют SDK v3.0 или выше. Процесс отображения пейволов различается для пейволов, созданных в разных версиях Paywall Builder, и для Remote Config пейволов.

Adapty React Native SDK предоставляет два способа отображения пейволов:

  • React-компонент: встроенный компонент, который можно интегрировать в архитектуру и систему навигации вашего приложения.

  • Модальное окно

React-компонент

Подход на основе React-компонента требует SDK версии 3.14.0 или выше.

Чтобы встроить пейвол в существующее дерево компонентов, используйте компонент AdaptyPaywallView непосредственно в иерархии React Native компонентов. Встроенный компонент позволяет интегрировать его в архитектуру приложения и систему навигации.

На Android, если пейвол не перекрывает строку состояния, вверху может появиться визуальный оверлей. Рекомендуем отключить его для своих пейволов. См. Визуальный оверлей в верхней части пейвола (Android).


function MyPaywall({ paywall }) {
  const paywallParams = useMemo(() => ({
    loadTimeoutMs: 3000,
  }), []);

  const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []);
  const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []);
  const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []);
  const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []);
  const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []);
  const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []);
  const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []);
  const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []);
  const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []);
  const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []);
  const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []);
  const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => {}, []);
  const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []);
  const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []);

  return (
    <AdaptyPaywallView
      paywall={paywall}
      params={paywallParams}
      style={styles.paywall}
      onCloseButtonPress={onCloseButtonPress}
      onProductSelected={onProductSelected}
      onPurchaseStarted={onPurchaseStarted}
      onPurchaseCompleted={onPurchaseCompleted}
      onPurchaseFailed={onPurchaseFailed}
      onRestoreStarted={onRestoreStarted}
      onRestoreCompleted={onRestoreCompleted}
      onRestoreFailed={onRestoreFailed}
      onPaywallShown={onPaywallShown}
      onRenderingFailed={onRenderingFailed}
      onLoadingProductsFailed={onLoadingProductsFailed}
      onCustomAction={onCustomAction}
      onUrlPress={onUrlPress}
      onWebPaymentNavigationFinished={onWebPaymentNavigationFinished}
    />
  );
}

Чтобы показать пейвол как отдельный экран, используйте метод view.present() на объекте view, созданном методом createPaywallView. Каждый view можно использовать только один раз. Если нужно показать пейвол повторно, вызовите createPaywallView ещё раз, чтобы создать новый экземпляр view.

Повторное использование одного и того же view без его пересоздания запрещено. Это приведёт к ошибке AdaptyUIError.viewAlreadyPresented.


const view = await createPaywallView(paywall);

// Optional: handle paywall events (close, purchase, restore, etc)
// view.setEventHandlers({ ... });

try {
  await view.present();
} catch (error) {
  // handle the error
}

Каждый вызов setEventHandlers перезаписывает ранее установленные обработчики — включая дефолтные и те, что вы задали раньше, для указанных событий.

Настройка стиля отображения на iOS

Настройте способ отображения пейвола на iOS, передав параметр iosPresentationStyle в метод present(). Параметр принимает значения 'full_screen' (по умолчанию) или 'page_sheet'.

try {
  await view.present({ iosPresentationStyle: 'page_sheet' });
} catch (error) {
  // handle the error
}

Использование таймера, заданного разработчиком

Чтобы использовать таймеры, заданные разработчиком, в мобильном приложении, используйте timerId — в данном примере CUSTOM_TIMER_NY, Timer ID таймера, заданного разработчиком, который вы установили в дашборде Adapty. Это позволяет приложению динамически обновлять таймер с правильным значением — например, 13d 09h 03m 34s (рассчитывается как время окончания таймера, например Новый год, минус текущее время).

В этом примере CUSTOM_TIMER_NY — это Timer ID таймера, заданного разработчиком в дашборде Adapty. timerResolver обеспечивает динамическое обновление таймера с нужным значением — например, 13d 09h 03m 34s (вычисляется как время окончания таймера, например Новый год, минус текущее время).

Отображение диалога

Используйте этот метод вместо нативных диалогов оповещений, когда на Android отображается пейвол. На Android обычные RN-алерты появляются позади пейвола и становятся невидимы для пользователей. Этот метод обеспечивает корректное отображение диалога поверх пейвола на всех платформах.

try {
  const action = await view.showDialog({
    title: 'Close paywall?',
    content: 'You will lose access to exclusive offers.',
    primaryActionTitle: 'Stay',
    secondaryActionTitle: 'Close',
  });

  if (action === 'secondary') {
    // User confirmed - close the paywall
    await view.dismiss();
  }
  // If primary - do nothing, user stays
} catch (error) {
  // handle error
}

Замена одной подписки на другую

Когда пользователь пытается купить новую подписку при наличии активной подписки на Android, вы можете управлять тем, как должная обрабатываться новая покупка, передав параметры обновления подписки при создании вью пейвола. Чтобы заменить текущую подписку на новую, используйте productPurchaseParams в createPaywallView с параметрами oldSubVendorProductId и prorationMode.


const productPurchaseParams = paywall.productIdentifiers.map((productId) => {
  let params = {};
  if (Platform.OS === 'android') {
    params.android = {
      subscriptionUpdateParams: {
        oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION',
        prorationMode: 'with_time_proration',
      },
    };
  }
  return { productId, params };
});

const view = await createPaywallView(paywall, { productPurchaseParams });

Устранение неполадок

Визуальный оверлей в верхней части пейвола (Android)

Этот параметр поддерживается начиная с React Native SDK 3.15.5 и доступен только в bare React Native-проектах.

Если вы используете управляемый воркфлоу Expo, добавить этот Android-ресурс напрямую не получится. Чтобы применить этот параметр, необходимо создать кастомный Expo config plugin, который добавляет соответствующий Android-ресурс, и зарегистрировать его в app.config.js. Это обязательно, поскольку Expo управляет нативным Android-проектом за вас.

Если AdaptyPaywallView не растягивается за пределы строки состояния, над ней всё равно может появляться визуальный оверлей. Чтобы убрать его, добавьте следующий булев ресурс в приложение:

  1. Перейдите в android/app/src/main/res/values. Если файла bools.xml нет — создайте его.

  2. Добавьте следующий ресурс:

<resources>
    <bool name="adapty_paywall_enable_safe_area_paddings">false</bool>
</resources>

Обратите внимание: изменения применяются глобально ко всем пейволам в приложении.