Hiển thị flows & paywalls - React Native

Hiển thị flows và paywalls
Flow Được tạo trong Flow Builder — render nguyên bản trên thiết bị, không cần WebView
Paywall của Paywall Builder Toàn bộ nội dung Paywall Builder hiện có

Nếu bạn đã tạo một flow hoặc paywall trong Flow Builder, bạn không cần lo lắng về việc render nó trong code ứng dụng để hiển thị cho người dùng. Flow đó đã bao gồm cả nội dung hiển thị lẫn cách thức hiển thị.

Trước khi bắt đầu, hãy đảm bảo rằng:

  1. Bạn đã tạo một flow hoặc paywall.
  2. Bạn đã thêm nó vào một placement.
  3. Bạn đã tải flow và chuẩn bị view.

Hướng dẫn này chỉ dành cho flow và paywall sử dụng Paywall Builder, yêu cầu SDK v4.0 trở lên. Quy trình hiển thị flow khác với paywall remote config.

Adapty React Native SDK cung cấp hai cách để hiển thị flow và paywall:

  • React component: Component nhúng cho phép tích hợp vào kiến trúc và hệ thống điều hướng của ứng dụng.

  • Modal presentation

Component React

Để nhúng một flow vào cây component hiện có của bạn, hãy sử dụng component AdaptyFlowView trực tiếp trong phân cấp component React Native. Component nhúng này cho phép bạn tích hợp nó vào kiến trúc và hệ thống điều hướng của ứng dụng.

Component AdaptyFlowView tạo view khi render, tức là khi cấu hình và hình ảnh được tải xuống. Để tải trước, hãy gọi createFlowView cho cùng một flow ở phần trước của ứng dụng. Component sẽ tái sử dụng dữ liệu đã cache và render mà không cần chờ tải xuống.


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

Để hiển thị một flow dưới dạng màn hình độc lập, hãy dùng phương thức view.present() trên view được tạo bởi phương thức createFlowView. Mỗi view chỉ có thể dùng một lần. Nếu bạn cần hiển thị flow lại, hãy gọi createFlowView thêm một lần nữa để tạo một view mới.

Không được tái sử dụng cùng một view mà không tạo lại. Điều này sẽ dẫn đến lỗi 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
}

Gọi setEventHandlers nhiều lần sẽ ghi đè các handler bạn đã cung cấp, thay thế cả handler mặc định lẫn các handler đã thiết lập trước đó cho những sự kiện cụ thể đó.

Cấu hình kiểu hiển thị trên iOS

Cấu hình cách flow được hiển thị trên iOS bằng cách truyền tham số iosPresentationStyle vào phương thức present(). Tham số này chấp nhận các giá trị 'full_screen' (mặc định) hoặc 'page_sheet'.

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

Sử dụng timer do developer tự định nghĩa

Để sử dụng timer do developer tự định nghĩa trong ứng dụng mobile, hãy dùng timerId, trong ví dụ này là CUSTOM_TIMER_NYTimer ID của timer mà bạn đã thiết lập trong Adapty dashboard. Điều này đảm bảo ứng dụng của bạn tự động cập nhật timer với giá trị chính xác — ví dụ như 13d 09h 03m 34s (được tính bằng thời điểm kết thúc của timer, chẳng hạn như Ngày đầu năm mới, trừ đi thời gian hiện tại).

Trong ví dụ này, CUSTOM_TIMER_NYTimer ID của bộ đếm thời gian do developer định nghĩa mà bạn đã thiết lập trong Adapty dashboard. timerResolver đảm bảo ứng dụng của bạn cập nhật động bộ đếm với giá trị chính xác—chẳng hạn như 13d 09h 03m 34s (được tính bằng thời điểm kết thúc của bộ đếm, ví dụ như Năm Mới, trừ đi thời gian hiện tại).

Hiển thị hộp thoại

Sử dụng phương thức này thay cho các hộp thoại alert mặc định khi một flow view đang được hiển thị trên Android. Trên Android, các alert thông thường của RN sẽ xuất hiện phía sau flow view, khiến người dùng không nhìn thấy. Phương thức này đảm bảo hộp thoại luôn hiển thị đúng vị trí, phía trên flow, trên tất cả các nền tảng.

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
}

Thay thế một gói đăng ký bằng gói khác

Khi người dùng cố mua một gói đăng ký mới trong khi đã có một gói đăng ký đang hoạt động trên Android, bạn có thể kiểm soát cách xử lý giao dịch mua mới bằng cách truyền tham số cập nhật gói đăng ký khi tạo flow view. Để thay thế gói đăng ký hiện tại bằng gói mới, hãy sử dụng productPurchaseParams trong createFlowView với các tham số oldSubVendorProductIdprorationMode.


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

Nếu bạn đã tùy chỉnh paywall bằng Paywall Builder, bạn không cần lo lắng về việc render nó trong code ứng dụng di động để hiển thị cho người dùng. Paywall đó đã bao gồm cả nội dung hiển thị lẫn cách hiển thị.

Trước khi bắt đầu, hãy đảm bảo rằng:

  1. Bạn đã tạo một paywall.
  2. Bạn đã thêm paywall vào một placement.
  3. Bạn đã lấy paywall và chuẩn bị view.

Hướng dẫn này chỉ dành cho paywall mới trong Paywall Builder, yêu cầu SDK v3.0 trở lên. Quy trình hiển thị paywall khác nhau tùy theo phiên bản Paywall Builder được sử dụng và paywall sử dụng Remote Config.

Adapty React Native SDK cung cấp hai cách để hiển thị paywall:

  • React component: Component được nhúng vào cho phép bạn tích hợp nó vào kiến trúc và hệ thống điều hướng của ứng dụng.

  • Modal presentation

React component

Cách tiếp cận React component yêu cầu SDK phiên bản 3.14.0 trở lên.

Để nhúng một paywall vào cây component hiện có của bạn, hãy sử dụng component AdaptyPaywallView trực tiếp trong hệ thống phân cấp component React Native. Component nhúng này cho phép bạn tích hợp nó vào kiến trúc và hệ thống điều hướng của ứng dụng.

Trên Android, nếu paywall không kéo dài ra phía sau thanh trạng thái, một lớp phủ trực quan có thể xuất hiện ở phía trên. Chúng tôi khuyến nghị bạn tắt tính năng này cho các paywall của mình. Xem Lớp phủ trực quan ở đầu paywall (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}
    />
  );
}

Để hiển thị paywall dưới dạng màn hình độc lập, hãy sử dụng phương thức view.present() trên view được tạo bởi phương thức createPaywallView. Mỗi view chỉ có thể được sử dụng một lần. Nếu bạn cần hiển thị lại paywall, hãy gọi createPaywallView thêm một lần nữa để tạo một view mới.

Không được phép tái sử dụng cùng một view mà không tạo lại. Việc này sẽ dẫn đến lỗi 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
}

Gọi setEventHandlers nhiều lần sẽ ghi đè các handler bạn đã cung cấp, thay thế cả handler mặc định lẫn các handler đã được thiết lập trước đó cho những sự kiện cụ thể đó.

Cấu hình kiểu hiển thị trên iOS

Cấu hình cách paywall được hiển thị trên iOS bằng cách truyền tham số iosPresentationStyle vào phương thức present(). Tham số này chấp nhận các giá trị 'full_screen' (mặc định) hoặc 'page_sheet'.

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

Sử dụng timer do lập trình viên định nghĩa

Để sử dụng timer do lập trình viên định nghĩa trong ứng dụng di động, hãy dùng timerId, trong ví dụ này là CUSTOM_TIMER_NYTimer ID của timer do lập trình viên định nghĩa mà bạn đã thiết lập trên Adapty dashboard. Điều này đảm bảo ứng dụng cập nhật động giá trị của timer với giá trị chính xác — chẳng hạn 13d 09h 03m 34s (được tính bằng thời điểm kết thúc của timer, ví dụ như ngày đầu năm mới, trừ đi thời gian hiện tại).

Trong ví dụ này, CUSTOM_TIMER_NYTimer ID của timer do developer định nghĩa mà bạn đã cài đặt trong Adapty dashboard. timerResolver đảm bảo ứng dụng của bạn cập nhật động timer với giá trị chính xác—chẳng hạn như 13d 09h 03m 34s (được tính bằng thời điểm kết thúc của timer, như Ngày Đầu Năm Mới, trừ đi thời gian hiện tại).

Hiển thị dialog

Sử dụng phương thức này thay cho các hộp thoại alert thông thường khi một paywall view đang hiển thị trên Android. Trên Android, các alert RN thông thường sẽ xuất hiện phía sau paywall view, khiến người dùng không thể nhìn thấy chúng. Phương thức này đảm bảo dialog được hiển thị đúng cách, phía trên paywall trên mọi nền tảng.

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
}

Thay thế một gói đăng ký bằng gói khác

Khi người dùng cố mua một gói đăng ký mới trong khi đang có gói đăng ký khác đang hoạt động trên Android, bạn có thể kiểm soát cách xử lý giao dịch mua mới bằng cách truyền các tham số cập nhật gói đăng ký khi tạo paywall view. Để thay thế gói đăng ký hiện tại bằng gói mới, hãy sử dụng productPurchaseParams trong createPaywallView với các tham số oldSubVendorProductIdprorationMode.


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

Khắc phục sự cố

Lớp phủ hình ảnh ở đầu paywall (Android)

Cài đặt này được hỗ trợ từ React Native SDK 3.15.5 trở lên và chỉ khả dụng trong các dự án React Native thuần (bare).

Nếu bạn đang sử dụng Expo managed workflow, bạn không thể thêm trực tiếp Android resource này. Để áp dụng cài đặt này, bạn cần tạo một Expo config plugin tùy chỉnh để thêm Android resource tương ứng và đăng ký nó trong app.config.js. Điều này là bắt buộc vì Expo quản lý native Android project thay cho bạn.

Nếu AdaptyPaywallView không mở rộng ra phía sau thanh trạng thái, một lớp phủ trực quan vẫn có thể xuất hiện ở phía trên. Để xóa nó, hãy thêm tài nguyên boolean sau vào ứng dụng của bạn:

  1. Truy cập vào android/app/src/main/res/values. Nếu chưa có file bools.xml, hãy tạo mới.

  2. Thêm tài nguyên sau:

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

Lưu ý rằng các thay đổi này áp dụng toàn cục cho tất cả các paywall trong ứng dụng của bạn.