Display flows & paywalls - Capacitor
If you’ve created a flow or paywall in the Flow Builder, you don’t need to worry about rendering it in your mobile app code to display it to the user. Such a flow contains both what should be shown within it and how it should be shown.
Before you start, ensure that:
- You have created a flow or paywall.
- You have added it to a placement.
- You have fetched the flow and prepared the view.
This guide is for flows and Paywall Builder paywalls only, which require SDK v4.0 or later. The process for presenting flows differs for remote config paywalls.
- For presenting remote config paywalls, see Render paywall designed by remote config.
To display a flow or paywall as a standalone screen, use the view.present() method on the view created by the createFlowView method. Each view can only be used once. If you need to display the flow again, call createFlowView one more time to create a new view instance.
Reusing the same view without recreating it is forbidden. It will result in an error.
import { createFlowView } from '@adapty/capacitor';
const view = await createFlowView(flow);
// Optional: handle flow events (close, purchase, restore, etc)
// await view.setEventHandlers({ ... });
try {
await view.present();
} catch (error) {
// handle the error
}Calling setEventHandlers multiple times will override the handlers you provide, replacing both default and previously set handlers for those specific events.
Configure iOS presentation style
Configure how the flow is presented on iOS by passing the iosPresentationStyle parameter to the present() method. The parameter accepts 'full_screen' (default) or 'page_sheet' values. On Android, flows are always displayed as a full-screen activity.
try {
await view.present({ iosPresentationStyle: 'page_sheet' });
} catch (error) {
// handle the error
}Use developer-defined timer
To use developer-defined timers in your mobile app, use the timerId, in this example, CUSTOM_TIMER_NY, the Timer ID of the developer-defined timer you set in the Adapty dashboard. It ensures your app dynamically updates the timer with the correct value—like 13d 09h 03m 34s (calculated as the timer’s end time, such as New Year’s Day, minus the current time).
const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) };
const view = await createFlowView(flow, { customTimers });In this example, CUSTOM_TIMER_NY is the Timer ID of the developer-defined timer you set in the Adapty dashboard. The timer ensures your app dynamically updates the timer with the correct value—like 13d 09h 03m 34s (calculated as the timer’s end time, such as New Year’s Day, minus the current time).
Show dialog
Use this method instead of native alert dialogs when a flow view is presented on Android. On Android, regular alerts appear behind the flow view, which makes them invisible to users. This method ensures proper dialog presentation above the flow on all platforms.
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
When a user attempts to purchase a new subscription while another subscription is active on Android, you can control how the new purchase should be handled by passing subscription update parameters when creating the flow view. To replace the current subscription with the new one, use productPurchaseParams in createFlowView with the oldSubVendorProductId and prorationMode parameters.
import { Capacitor } from '@capacitor/core';
import { createFlowView } from '@adapty/capacitor';
import type { MakePurchaseParamsInput } from '@adapty/capacitor';
const productPurchaseParams = flow.paywalls
.flatMap((paywall) => paywall.productIdentifiers)
.map((productId) => {
const params: MakePurchaseParamsInput = {};
if (Capacitor.getPlatform() === '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 });If you’ve customized a paywall using the Paywall Builder, you don’t need to worry about rendering it in your mobile app code to display it to the user. Such a paywall contains both what should be shown within the paywall and how it should be shown.
This guide is for Paywall Builder paywalls only. The process for presenting paywalls differs for remote config paywalls. For presenting remote config paywalls, see Render paywall designed by remote config.
To display a paywall, use the view.present() method on the view created by the createPaywallView method. Each view can only be used once. If you need to display the paywall again, call createPaywallView one more time to create a new view instance.
Reusing the same view without recreating it may result in an error.
import { adapty, createPaywallView } from '@adapty/capacitor';
const view = await createPaywallView(paywall);
view.setEventHandlers({
onUrlPress(url) {
window.open(url, '_blank');
return false;
},
});
try {
await view.present();
} catch (error) {
// handle the error
}Use developer-defined timer
To use developer-defined timers in your mobile app, use the timerId, in this example, CUSTOM_TIMER_NY, the Timer ID of the developer-defined timer you set in the Adapty dashboard. It ensures your app dynamically updates the timer with the correct value—like 13d 09h 03m 34s (calculated as the timer’s end time, such as New Year’s Day, minus the current time).
const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) };
const view = await createPaywallView(paywall, { customTimers });In this example, CUSTOM_TIMER_NY is the Timer ID of the developer-defined timer you set in the Adapty dashboard. The timer ensures your app dynamically updates the timer with the correct value—like 13d 09h 03m 34s (calculated as the timer’s end time, such as New Year’s Day, minus the current time).
Show dialog
Use this method instead of native alert dialogs when a paywall view is presented on Android. On Android, regular alerts appear behind the paywall view, which makes them invisible to users. This method ensures proper dialog presentation above the paywall on all platforms.
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
}Configure iOS presentation style
Configure how the paywall is presented on iOS by passing the iosPresentationStyle parameter to the present() method. The parameter accepts 'full_screen' (default) or 'page_sheet' values.
await view.present({ iosPresentationStyle: 'page_sheet' });