---
title: "展示流程与付费墙 - React Native"
description: "在 React Native 应用中使用 Adapty 向用户展示流程与付费墙。"
---

<MethodPromo method="getFlow" label="展示流程与付费墙" />
如果您已在 Flow Builder 中创建了流程或付费墙，则无需在移动端应用代码中手动处理其渲染逻辑来向用户展示它。此类流程已包含展示内容及展示方式的完整配置。

开始之前，请确认以下几点：

1. 您已[创建流程或付费墙](create-paywall)。
2. 您已将其添加到[版位](placements)。
3. 您已[获取流程并准备好视图](react-native-get-pb-paywalls)。

:::warning
本指南仅适用于**流程和付费墙编辑工具付费墙**，需要 SDK v4.0 或更高版本。展示流程的方式与远程配置付费墙有所不同。

- 如需展示**远程配置付费墙**，请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls)。

:::

Adapty React Native SDK 提供两种展示流程和付费墙的方式：

- **React 组件**：嵌入式组件，可集成到应用的架构和导航体系中。

- **模态弹窗**
## React 组件 \{#react-component\}

要将流程嵌入到现有的组件树中，可以直接在 React Native 组件层级里使用 `AdaptyFlowView` 组件。嵌入式组件让你能够将其集成到应用的架构和导航系统中。
:::tip
`AdaptyFlowView` 组件在渲染时会创建视图，此时会加载配置和图片。如需预加载，可在应用更早的位置针对同一流程调用 [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration)。组件随后会复用缓存数据，无需等待下载即可完成渲染。
:::
```typescript showLineNumbers title="React Native (TSX)"

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}
    />
  );
}
```

## 模态展示 \{#modal-presentation\}

要将流程作为独立屏幕显示，请在由 [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示该流程，请重新调用 `createFlowView` 创建一个新的 `view` 实例。

:::warning
禁止在不重新创建的情况下复用同一个 `view`，否则将导致 `AdaptyUIError.viewAlreadyPresented` 错误。
:::
```typescript showLineNumbers title="React Native (TSX)"

const view = await createFlowView(flow);

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

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

:::important
多次调用 `setEventHandlers` 会覆盖你之前设置的处理函数，替换掉这些特定事件的默认处理函数及先前设置的处理函数。
:::
### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\}

通过向 `present()` 方法传递 `iosPresentationStyle` 参数，可以配置流程在 iOS 上的呈现方式。该参数接受 `'full_screen'`（默认值）或 `'page_sheet'` 两个值。

```typescript showLineNumbers
try {
  await view.present({ iosPresentationStyle: 'page_sheet' });
} catch (error) {
  // handle the error
}
```
## 使用开发者自定义计时器 \{#use-developer-defined-timer\}

要在移动应用中使用开发者自定义计时器，请使用 `timerId`，本例中为 `CUSTOM_TIMER_NY`，即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。它能确保应用动态更新计时器并显示正确的值，例如 `13d 09h 03m 34s`（计算方式为计时器的结束时间（如元旦）减去当前时间）。
<Tabs>
<TabItem value="component" label="React component">
```typescript showLineNumbers title="React Native (TSX)"
const flowParams = {
  customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }
};

<AdaptyFlowView
  flow={flow}
  params={flowParams}
  // ... your event handlers
/>
```
</TabItem>
<TabItem value="modal" label="Modal presentation">
```typescript showLineNumbers title="React Native (TSX)"
const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) };

const view = await createFlowView(flow, { customTimers });
```
</TabItem>
</Tabs>
在此示例中，`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 会确保你的应用动态更新计时器，显示正确的值——例如 `13d 09h 03m 34s`（由计时器的结束时间（如元旦）减去当前时间计算得出）。
## 显示对话框 \{#show-dialog\}

在 Android 上展示流程视图时，请使用此方法代替原生 alert 对话框。在 Android 上，普通 RN 弹窗会显示在流程视图后面，导致用户看不到。此方法可确保对话框在所有平台上都能正确显示在流程上方。
```typescript showLineNumbers title="React Native (TSX)"
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
}
```

## 用新订阅替换旧订阅 \{#replace-one-subscription-with-another\}

当用户在 Android 上已有活跃订阅的情况下尝试购买新订阅时，你可以通过在创建 flow 视图时传入订阅更新参数来控制新购买的处理方式。若要用新订阅替换当前订阅，请在 `createFlowView` 中使用 `productPurchaseParams`，并传入 `oldSubVendorProductId` 和 `prorationMode` 参数。
```typescript showLineNumbers title="React Native (TSX)"

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 });
```

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

如果你使用付费墙编辑工具自定义了付费墙，就无需在移动应用代码中手动渲染它来向用户展示。这类付费墙已包含展示内容和展示方式的完整配置。

开始之前，请确认：

1. 你已[创建付费墙](create-paywall)。
2. 你已将付费墙添加到[版位](placements)。
3. 你已[获取付费墙并准备好视图](react-native-get-pb-paywalls)。

:::warning
本指南仅适用于**新版付费墙编辑工具付费墙**，需要 SDK v3.0 或更高版本。不同版本付费墙编辑工具设计的付费墙以及远程配置付费墙的展示流程有所不同。

- 如需展示**远程配置付费墙**，请参阅[展示远程配置设计的付费墙](present-remote-config-paywalls)。

:::

Adapty React Native SDK 提供两种展示付费墙的方式：

- **React 组件**：嵌入式组件，可集成到应用的架构和导航系统中。

- **模态展示**
## React 组件 \{#react-component\}

:::note
**React 组件**方式需要 SDK 3.14.0 或更高版本。
:::

要将付费墙嵌入到现有的组件树中，可以直接在 React Native 组件层级中使用 `AdaptyPaywallView` 组件。嵌入式组件允许你将其集成到应用的架构和导航系统中。
:::note
在 Android 上，如果付费墙未延伸到状态栏后方，其顶部可能会出现视觉遮罩。我们建议您为付费墙关闭此效果。请参阅[付费墙顶部的视觉遮罩（Android）](#visual-overlay-at-the-top-of-the-paywall-android)。
:::
```typescript showLineNumbers title="React Native (TSX)"

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}
    />
  );
}
```

## 弹窗展示 \{#modal-presentation\}

要将付费墙作为独立页面展示，请在由 [`createPaywallView`](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示付费墙，请重新调用 `createPaywallView` 创建一个新的 `view` 实例。

:::warning
禁止在不重新创建的情况下复用同一个 `view`，否则会触发 `AdaptyUIError.viewAlreadyPresented` 错误。
:::
```typescript showLineNumbers title="React Native (TSX)"

const view = await createPaywallView(paywall);

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

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

:::important
多次调用 `setEventHandlers` 会覆盖你之前设置的处理器，包括默认处理器和针对特定事件已设置的处理器。
:::
### 配置 iOS 展示样式 \{#configure-ios-presentation-style\}

通过向 `present()` 方法传入 `iosPresentationStyle` 参数，可以配置付费墙在 iOS 上的展示方式。该参数接受 `'full_screen'`（默认值）或 `'page_sheet'` 两个值。

```typescript showLineNumbers
try {
  await view.present({ iosPresentationStyle: 'page_sheet' });
} catch (error) {
  // handle the error
}
```
## 使用开发者自定义计时器 \{#use-developer-defined-timer\}

要在移动应用中使用开发者自定义计时器，请使用 `timerId`——在本示例中为 `CUSTOM_TIMER_NY`，即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。这样可以确保应用动态地以正确的值更新计时器——例如 `13d 09h 03m 34s`（由计时器的结束时间（如元旦）减去当前时间计算得出）。
<Tabs>
<TabItem value="component" label="React component">
```typescript showLineNumbers title="React Native (TSX)"
const paywallParams = {
  customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }
};

<AdaptyPaywallView
  paywall={paywall}
  params={paywallParams}
  // ... your event handlers
/>
```
</TabItem>
<TabItem value="modal" label="Modal presentation">
```typescript showLineNumbers title="React Native (TSX)"
const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) };

const view = await createPaywallView(paywall, { customTimers });
```
</TabItem>
</Tabs>
在此示例中，`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 会确保你的应用动态更新计时器，显示正确的值——例如 `13d 09h 03m 34s`（由计时器的结束时间（如元旦）减去当前时间得出）。
## 显示对话框 \{#show-dialog\}

在 Android 上展示付费墙视图时，请使用此方法替代原生的 alert 对话框。在 Android 上，普通的 RN alert 会出现在付费墙视图的后面，导致用户看不到。此方法可确保对话框在所有平台上都能正确显示在付费墙的上层。
```typescript showLineNumbers title="React Native (TSX)"
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
}
```

## 用新订阅替换旧订阅 \{#replace-one-subscription-with-another\}

当用户在 Android 上已有有效订阅时尝试购买新订阅，你可以在创建付费墙视图时传入订阅更新参数，控制新购买的处理方式。如需将当前订阅替换为新订阅，请在 `createPaywallView` 中使用 `productPurchaseParams`，并传入 `oldSubVendorProductId` 和 `prorationMode` 参数。
```typescript showLineNumbers title="React Native (TSX)"

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 });
```

## 故障排查 \{#troubleshooting\}
### 付费墙顶部的视觉遮罩层（Android）\{#visual-overlay-at-the-top-of-the-paywall-android\}

:::note
此设置从 React Native SDK 3.15.5 起支持，且仅适用于纯 React Native 项目。

如果你使用的是 Expo 托管工作流，则无法直接添加此 Android 资源。要应用此设置，必须创建一个自定义 Expo config plugin，将相应的 Android 资源添加其中，并在 app.config.js 中注册。这是必要的，因为 Expo 会替你管理原生 Android 项目。
:::
如果 `AdaptyPaywallView` 没有延伸到状态栏后面，其顶部可能仍会出现视觉遮罩。要去除该遮罩，请在你的应用中添加以下布尔资源：

1. 前往 `android/app/src/main/res/values`。如果不存在 `bools.xml` 文件，请新建一个。

2. 添加以下资源：

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

请注意，该更改会全局应用于应用中的所有付费墙。

---