Hiển thị flows & paywalls - React Native
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:
- Bạn đã tạo một flow hoặc paywall.
- Bạn đã thêm nó vào một placement.
- 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.
- Để hiển thị paywall remote config, xem Hiển thị paywall thiết kế bằng 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}
/>
);
}Trình bày dạng modal
Để 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_NY — Timer 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_NY là Timer 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ố oldSubVendorProductId và 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 });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:
- Bạn đã tạo một paywall.
- Bạn đã thêm paywall vào một placement.
- 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.
- Để hiển thị paywall sử dụng Remote Config, xem Hiển thị paywall được thiết kế bằ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}
/>
);
}Trình bày dạng modal
Để 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_NY — Timer 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_NY là Timer 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ố oldSubVendorProductId và 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 });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:
-
Truy cập vào
android/app/src/main/res/values. Nếu chưa có filebools.xml, hãy tạo mới. -
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.