Displaying Paywalls
iOS
How to display a fullscreen Paywall in your app
RevenueCat Paywalls will, by default, show paywalls fullscreen and there are multiple ways to do this with SwiftUI and UIKit.
- Depending on an entitlement with
presentPaywallIfNeeded
- Custom logic with
presentPaywallIfNeeded
- Manually with
PaywallView
orPaywallViewController
- Entitlement
- Custom Logic
- Manually
- Manually (UIKit)
- Manually (UIKit and Objective-C)
import SwiftUI
import RevenueCat
import RevenueCatUI
struct App: View {
var body: some View {
ContentView()
.presentPaywallIfNeeded(
requiredEntitlementIdentifier: "pro",
purchaseCompleted: { customerInfo in
print("Purchase completed: \(customerInfo.entitlements)")
},
restoreCompleted: { customerInfo in
// Paywall will be dismissed automatically if "pro" is now active.
print("Purchases restored: \(customerInfo.entitlements)")
}
)
}
}
import SwiftUI
import RevenueCat
import RevenueCatUI
struct App: View {
var body: some View {
ContentView()
.presentPaywallIfNeeded { customerInfo in
// Returning `true` will present the paywall
return customerInfo.entitlements.active.keys.contains("pro")
} purchaseCompleted: { customerInfo in
print("Purchase completed: \(customerInfo.entitlements)")
} restoreCompleted: {
// Paywall will be dismissed automatically if "pro" is now active.
print("Purchases restored: \(customerInfo.entitlements)")
}
}
}
import SwiftUI
import RevenueCat
import RevenueCatUI
struct App: View {
@State
var displayPaywall = false
var body: some View {
ContentView()
.sheet(isPresented: self.$displayPaywall) {
PaywallView(displayCloseButton: true)
}
}
}
import UIKit
import RevenueCat
import RevenueCatUI
class ViewController: UIViewController {
@IBAction func presentPaywall() {
let controller = PaywallViewController()
controller.delegate = self
present(controller, animated: true, completion: nil)
}
}
extension ViewController: PaywallViewControllerDelegate {
func paywallViewController(_ controller: PaywallViewController,
didFinishPurchasingWith customerInfo: CustomerInfo) {
}
}
#import "ViewController.h"
@import RevenueCat;
@import RevenueCatUI;
@interface ViewController () <RCPaywallViewControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)showPaywallTapped:(id)sender {
[RCPurchases.sharedPurchases offeringsWithCompletionHandler:^(RCOfferings * _Nullable offerings, NSError * _Nullable error) {
if (error) {
NSLog(@"Error fetching offerings: %@", error.localizedDescription);
return;
}
RCOffering *offering = offerings.current;
if (offering) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Current offering identifier: %@", offering.identifier);
RCPaywallViewController *controller = [[RCPaywallViewController alloc] initWithOffering:offering
displayCloseButton:YES
shouldBlockTouchEvents:NO
dismissRequestedHandler:^(RCPaywallViewController * _Nonnull controller) {
NSLog(@"dismiss request!");
[controller dismissViewControllerAnimated:YES completion:nil];
}];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
});
} else {
NSLog(@"No current offering available");
}
}];
}
#pragma mark - PaywallViewControllerDelegate
- (void)paywallViewController:(RCPaywallViewController *)controller
didFinishPurchasingWithCustomerInfo:(RCCustomerInfo *)customerInfo {
// Handle purchase completion here
}
@end
Close Button
Paywalls displayed with presentPaywallIfNeeded
will have a close button on the presented sheet.
However, a PaywallView
will not have a close button by default. This gives you full control on how to to navigate to and from your PaywallView
. You can push it onto an existing navigation stack or show it in a sheet with a custom dismiss button using SwiftUI toolbar.
If desired, you can pass displayCloseButton: true
when creating PaywallView
to display a close button automatically.
This close button will automatically take the color of your app's tint color. You can also override it by using View.tintColor
: PaywallView(...).tintColor(Color.red)
.
You can also set .onRequestedDismissal()
PaywallView's modifier to modify the dismissal behavior of the PaywallView. By default, the paywall will close automatically when completing a purchase, or when pressing the close button. You can use this modifier to customize the behavior of the close button. If you're using UIKit there's also a dismissRequestedHandler
parameter in the PaywallViewController
initializer that you can use to handle the dismissal of the paywall.
How to display a footer Paywall on your custom paywall
RevenueCatUI also has a paywall variation that can be displayed as a footer below your custom paywall. This allows you to design your paywall exactly as you want with native components while still using RevenueCat UI to handle it. This is done by adding the .paywallFooter()
view modifier to your view.
The footer paywall mainly consists of the following:
- Purchase button
- Package details text
- Package selection (if there are any multiple packages configured)
This is all remotely configured and RevenueCatUI handles all the intro offer eligibility and purchase logic.
- Current Offering
- Specific Offering
import SwiftUI
import RevenueCat
import RevenueCatUI
struct YourPaywall: View {
var body: some View {
ScrollView {
// Your custom paywall design content
}
.paywallFooter()
}
}
import SwiftUI
import RevenueCat
import RevenueCatUI
struct YourPaywall: View {
let offering: Offering
var body: some View {
ScrollView {
// Your custom paywall design content
}
.paywallFooter(offering: offering, condensed: true) { customerInfo in
// Purchase completed! Thank your user and dismiss your paywall
}
}
}
How to use custom fonts
Paywalls can be configured to use the same font as your app using a PaywallFontProvider
. A PaywallFontProvider
can be passed as an argument into all methods for displaying the paywall.
By default, a paywall will use the DefaultPaywallFontProvider
. This uses the system default font which supports dynamic type.
We also offer a CustomPaywallFontProvider
which requires a font name that could be something like "Arial" or "Papyrus".
If you need more control over your font preferences, you can create your own PaywallFontProvider
. One of the following examples will use a rounded system font in the paywall.
- By Font Name
- Manual PaywallFontProvider
import SwiftUI
import RevenueCat
import RevenueCatUI
struct App: View {
var body: some View {
ContentView()
.presentPaywallIfNeeded(
fonts: CustomPaywallFontProvider(fontName: "Arial")
) { customerInfo in
// Returning `true` will present the paywall
return customerInfo.entitlements.active.keys.contains("pro")
}
}
}
import SwiftUI
import RevenueCat
import RevenueCatUI
struct App: View {
var body: some View {
ContentView()
.presentPaywallIfNeeded(
fonts: RoundedSystemFontProvider()
) { customerInfo in
// Returning `true` will present the paywall
return customerInfo.entitlements.active.keys.contains("pro")
}
}
}
class RoundedSystemFontProvider: PaywallFontProvider {
func font(for textStyle: Font.TextStyle) -> Font {
return Font.system(textStyle, design: .rounded)
}
}
Android
How to display a fullscreen Paywall in your app
RevenueCat Paywalls will, by default, show paywalls fullscreen and there are multiple ways to do this with Activity
s and Jetpack Compose.
- Depending on an entitlement with
PaywallDialog
- Custom logic with
PaywallDialog
- Manually with
Paywall
,PaywallDialog
, orPaywallActivityLauncher
- Entitlement
- Custom Logic
- Manually
- Manually (Activity)
- Manually (Activity) - Java
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun LockedScreen() {
YourContent()
PaywallDialog(
PaywallDialogOptions.Builder()
.setRequiredEntitlementIdentifier(Constants.ENTITLEMENT_ID)
.setListener(
object : PaywallListener {
override fun onPurchaseCompleted(customerInfo: CustomerInfo, storeTransaction: StoreTransaction) {}
override fun onRestoreCompleted(customerInfo: CustomerInfo) {}
}
)
.build()
)
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screen.Main.route,
) {
composable(route = Screen.Main.route) {
MainScreen()
PaywallDialog(
PaywallDialogOptions.Builder()
.setShouldDisplayBlock { !it.entitlements.active.isEmpty() }
.setListener(
object : PaywallListener {
override fun onPurchaseCompleted(customerInfo: CustomerInfo, storeTransaction: StoreTransaction) {}
override fun onRestoreCompleted(customerInfo: CustomerInfo) {}
}
)
.build()
)
}
}
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun NavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screen.Main.route,
) {
composable(route = Screen.Main.route) {
MainScreen()
}
composable(route = Screen.Paywall.route) {
Paywall(
options = PaywallOptions.Builder(
onDismiss = { navController.popBackStack() }
)
.setListener(
object : PaywallListener {
override fun onPurchaseCompleted(customerInfo: CustomerInfo, storeTransaction: StoreTransaction) {}
override fun onRestoreCompleted(customerInfo: CustomerInfo) {}
}
)
.build()
)
}
}
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
class MainActivity : AppCompatActivity(), PaywallResultHandler {
private lateinit var paywallActivityLauncher: PaywallActivityLauncher
private lateinit var root: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
paywallActivityLauncher = PaywallActivityLauncher(this, this)
}
private fun launchPaywallActivity() {
paywallActivityLauncher.launchIfNeeded(requiredEntitlementIdentifier = Constants.ENTITLEMENT_ID)
}
override fun onActivityResult(result: PaywallResult) {}
}
@OptIn(markerClass = ExperimentalPreviewRevenueCatUIPurchasesAPI.class)
public class MainActivity extends AppCompatActivity implements PaywallResultHandler {
private PaywallActivityLauncher launcher;
private static final String requiredEntitlementIdentifier = "MY_ENTITLEMENT_ID";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
launcher = new PaywallActivityLauncher(this, this);
}
private void launchPaywallActivity() {
// This will launch the paywall only if the user doesn't have the given entitlement id active.
launcher.launchIfNeeded(requiredEntitlementIdentifier);
// or if you want to launch it without any conditions
launcher.launch();
}
@Override
public void onActivityResult(PaywallResult result) {
// Handle result
}
}
Close Button
Paywalls displayed with PaywallDialog
will have a close button on the presented sheet.
However, a Paywall
will not have a close button by default. This gives you full control over how to navigate to and from your Paywall
composable. You can push this to the existing navigation stack or display it in a sheet with a custom dismiss button using Android's navigation components and fragments.
When presenting the paywall using the PaywallActivityLauncher
, the close button will be shown by default since the paywall will be displayed as a new activity on top of the current stack.
If desired, you can use PaywallOptions.Builder
's setShouldDisplayDismissButton(true)
method when creating a Paywall
to display a close button automatically.
How to display a footer Paywall on your custom paywall
RevenueCatUI also has a paywall variation that can be displayed as a footer below your custom paywall. This allows you to design your paywall exactly as you want with native components while still using RevenueCat UI to handle it.
This is done by using the PaywallFooter
composable.
The footer paywall mainly consists of the following:
- Purchase button
- Package details text
- Package selection (if there are any multiple packages configured)
This is all remotely configured and RevenueCatUI handles all the intro offer eligibility and purchase logic.
- Current Offering
- Specific Offering
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun PaywallScreen(dismissRequest: () -> Unit) {
PaywallFooter(
options = PaywallOptions.Builder(dismissRequest).build()
) {
CustomPaywallContent()
}
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun PaywallScreen(offering: Offering, dismissRequest: () -> Unit) {
PaywallFooter(
options = PaywallOptions.Builder(
options = PaywallOptions.Builder(dismissRequest).build()
)
.setOffering(offering)
.build()
) {
CustomPaywallContent()
}
}
How to use custom fonts
Paywalls can be configured to use the same font as your app using a FontProvider
when using Compose or ParcelizableFontProvider
when using PaywallActivityLauncher
. These can be passed to the PaywallOptions
or PaywallDialogOptions
builders or directly to the launch
method in PaywallActivityLauncher
into all methods for displaying the paywall.
By default, a paywall will not use a font provider. This uses the current Material3 theme's Typography by default.
We offer a CustomFontProvider
and CustomParcelizableFontProvider
which receives a single font family to use by all text styles in the paywall, if you don't need extra granularity control.
If you need more control over your font preferences, you can create your own FontProvider
. See the following examples for some common use cases:
- Compose single font
- Compose font per style
- Activity single resource font
- Activity single Google font
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
fun MyComposable() {
PaywallDialog(
PaywallDialogOptions.Builder { /* on dismiss */ }
.setFontProvider(CustomFontProvider(myFontFamily))
.build()
)
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
fun MyComposable() {
PaywallDialog(
PaywallDialogOptions.Builder { /* on dismiss */ }
.setFontProvider(object : FontProvider {
override fun getFont(type: TypographyType): FontFamily? {
return when (type) {
TypographyType.HEADLINE_LARGE -> headlineFontFamily
TypographyType.BODY_LARGE -> bodyFontFamily
else -> null // Will use default font
}
}
})
.build()
)
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
class MyActivity : ComponentActivity(), PaywallResultHandler {
private lateinit var paywallActivityLauncher: PaywallActivityLauncher
private val fontFamily = PaywallFontFamily(
fonts = listOf(
PaywallFont.ResourceFont(R.font.my_font)
)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
paywallActivityLauncher = PaywallActivityLauncher(this, this)
}
fun launchPaywall(offering: Offering? = null) {
paywallActivityLauncher.launch(
offering,
CustomParcelizableFontProvider(fontFamily)
)
}
}
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
class MyActivity : ComponentActivity(), PaywallResultHandler {
private lateinit var paywallActivityLauncher: PaywallActivityLauncher
private val googleFontProvider = GoogleFontProvider(R.array.com_google_android_gms_fonts_certs)
private val fontFamily = PaywallFontFamily(
fonts = listOf(
PaywallFont.GoogleFont("GOOGLE_FONT_NAME", googleFontProvider)
)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
paywallActivityLauncher = PaywallActivityLauncher(this, this)
}
fun launchPaywall(offering: Offering? = null) {
paywallActivityLauncher.launch(
offering,
CustomParcelizableFontProvider(fontFamily)
)
}
}
React Native
How to display a fullscreen Paywall in your app
There are several ways to present paywalls:
- Using
RevenueCatUI.presentPaywall
: this will display a paywall when invoked. - Using
RevenueCatUI.presentPaywallIfNeeded
: this will present a paywall only if the customer does not have an unlocked entitlement. - Manually presenting
<RevenueCatUI.Paywall>
: this gives you more flexibility on how the paywall is presented.
- RevenueCatUI.presentPaywall
- RevenueCatUI.Paywall
import RevenueCatUI, { PAYWALL_RESULT } from "react-native-purchases-ui";
async function presentPaywall(): Promise<boolean> {
// Present paywall for current offering:
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywall();
// or if you need to present a specific offering:
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywall({
offering: offering // Optional Offering object obtained through getOfferings
});
switch (paywallResult) {
case PAYWALL_RESULT.NOT_PRESENTED:
case PAYWALL_RESULT.ERROR:
case PAYWALL_RESULT.CANCELLED:
return false;
case PAYWALL_RESULT.PURCHASED:
case PAYWALL_RESULT.RESTORED:
return true;
default:
return false;
}
}
async function presentPaywallIfNeeded() {
// Present paywall for current offering:
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywallIfNeeded({
requiredEntitlementIdentifier: "pro"
});
// If you need to present a specific offering:
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywallIfNeeded({
offering: offering, // Optional Offering object obtained through getOfferings
requiredEntitlementIdentifier: "pro"
});
}
import React from 'react';
import { View } from 'react-native';
import RevenueCatUI from 'react-native-purchases-ui';
// Display current offering
return (
<View style={{ flex: 1 }}>
<RevenueCatUI.Paywall
onDismiss={() => {
// Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
// Will be called when the close button is pressed (if enabled) or when a purchase succeeds.
}}
/>
</View>
);
// If you need to display a specific offering:
return (
<View style={{ flex: 1 }}>
<RevenueCatUI.Paywall
options={{
offering: offering // Optional Offering object obtained through getOfferings
}}
onRestoreCompleted={({customerInfo}: { customerInfo: CustomerInfo }) => {
// Optional listener. Called when a restore has been completed.
// This may be called even if no entitlements have been granted.
}
onDismiss={() => {
// Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
// Will be called when the close button is pressed (if enabled) or when a purchase succeeds.
}}
/>
</View>
);
If desired, you can pass displayCloseButton: true
inside the options
method when creating a PaywallView
to display a close button in the paywall. You will need to react by using the onDismiss
listener.
There are also several listeners that can be used to handle the paywall lifecycle, such as onPurchaseStarted
, onPurchaseCompleted
, and onRestoreStarted
.
How to display a footer Paywall on your custom paywall
RevenueCatUI also has a paywall variation that can be displayed as a footer below your custom paywall.
This allows you to design your paywall exactly as you want with native components while still using RevenueCat UI to handle it.
This is done by using <RevenueCatUI.PaywallFooterContainerView>
.
The footer paywall mainly consists of the following:
- Purchase button
- Package details text
- Package selection (if there are any multiple packages configured)
This is all remotely configured and RevenueCatUI handles all the intro offer eligibility and purchase logic.
- RevenueCatUI.PaywallFooterContainerView
import React from 'react';
import { Text } from 'react-native';
import RevenueCatUI from 'react-native-purchases-ui';
// Display current offering
return (
<RevenueCatUI.PaywallFooterContainerView>
<Text>
Your Custom Paywall Design
</Text>
</RevenueCatUI.PaywallFooterContainerView>
);
// If you need to display a specific offering:
return (
<RevenueCatUI.PaywallFooterContainerView
options={{
offering: offering // Optional Offering object obtained through getOfferings
}}
onRestoreCompleted={({customerInfo}: { customerInfo: CustomerInfo }) => {
// Optional listener. Called when a restore has been completed.
// This may be called even if no entitlements have been granted.
}
onDismiss={() => {
// Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
}}>
<Text>
Your Custom Paywall Design
</Text>
</RevenueCatUI.PaywallFooterContainerView>
);
PaywallFooterContainerView
only works correctly when phone is in portrait mode.
Landscape mode support for PaywallFooterContainerView
is coming soon.
Listeners
When using RevenueCatUI.Paywall
or RevenueCatUI.PaywallFooterContainerView
, you may use one of the provided listeners to react to user actions.
Available listeners at this time are:
- onPurchaseStarted
- onPurchaseCompleted
- onPurchaseError
- onPurchaseCancelled
- onRestoreStarted
- onRestoreCompleted
- onRestoreError
- onDismiss
How to use custom fonts
Paywalls can be configured to use the same font as your app passing the font family name in the FullScreenPaywallViewOptions
or FooterPaywallViewOptions
.
By default, a paywall will use the the system default font which supports dynamic type.
In order to add a font family, add it in your react native app and make sure to run npx react-native-asset
so it's added to the native components. Supported font types are .ttf
and .otf
.
Make sure the file names follow the convention:
- Regular: MyFont.ttf/MyFont.otf
- Bold: MyFont_bold.ttf/MyFont_bold.otf
- Italic: MyFont_italic.ttf/MyFont_italic.otf
- Bold and Italic: MyFont_bold_italic.ttf/MyFont_bold_italic.otf
See the following examples for some common use cases:
- Full Screen
- Footer
- presentPaywall
import React from 'react';
import { View } from 'react-native';
import RevenueCatUI from 'react-native-purchases-ui';
return (
<View style={{ flex: 1 }}>
<RevenueCatUI.Paywall options={{
fontFamily: "MyFont"
}} />
</View>
);
import React from 'react';
import { Text } from 'react-native';
import RevenueCatUI from 'react-native-purchases-ui';
return (
<RevenueCatUI.PaywallFooterContainerView
options={{
fontFamily: "MyFont"
}}
>
<Text>
Your Custom Paywall Design
</Text>
</RevenueCatUI.PaywallFooterContainerView>
);
import RevenueCatUI, { PAYWALL_RESULT } from "react-native-purchases-ui";
async function presentPaywall(): Promise<boolean> {
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywall({
fontFamily: 'Ubuntu',
});
}
async function presentPaywallIfNeeded() {
const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywallIfNeeded({
requiredEntitlementIdentifier: "pro",
fontFamily: 'Ubuntu',
});
}
Flutter
How to display a fullscreen Paywall in your app
There are several ways to present paywalls:
- Using
RevenueCatUI.presentPaywall
: this will display a paywall when invoked. - Using
RevenueCatUI.presentPaywallIfNeeded
: this will present a paywall only if the customer does not have an unlocked entitlement. - Manually presenting
PaywallView
: this gives you more flexibility on how the paywall is presented.
- RevenueCatUI.presentPaywall
- PaywallView
import 'dart:async';
import 'dart:developer';
import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
void presentPaywall() async {
final paywallResult = await RevenueCatUI.presentPaywall();
log('Paywall result: $paywallResult');
}
void presentPaywallIfNeeded() async {
final paywallResult = await RevenueCatUI.presentPaywallIfNeeded("pro");
log('Paywall result: $paywallResult');
}
import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
// Note: Avoid placing PaywallView inside a modal or bottom sheet (e.g., using showModalBottomSheet).
// Instead, include it directly in your widget.
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: PaywallView(
offering: offering, // Optional Offering object obtained through getOfferings
onRestoreCompleted: (CustomerInfo customerInfo) {
// Optional listener. Called when a restore has been completed.
// This may be called even if no entitlements have been granted.
}
onDismiss: () {
// Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
// Will be called when the close button is pressed (if enabled) or when a purchase succeeds.
},
),
),
),
);
}
If desired, you can pass displayCloseButton: true
when creating a PaywallView
to display a close button in the paywall.
You will need to react by using the onDismiss
listener.
How to display a footer Paywall on your custom paywall
purchases_ui_flutter also has a paywall variation that can be displayed as a footer below your custom paywall.
This allows you to design your paywall exactly as you want with native components while still using RevenueCat UI to handle it.
This is done by using PaywallFooterView
.
The footer paywall mainly consists of the following:
- Purchase button
- Package details text
- Package selection (if there are any multiple packages configured)
This is all remotely configured and RevenueCatUI handles all the intro offer eligibility and purchase logic.
- PaywallFooterView
import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
// Note: Avoid placing PaywallFooterView inside a modal or bottom sheet (e.g., using showModalBottomSheet).
// Instead, include it directly in your widget.
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: PaywallFooterView(
offering: offering, // Optional Offering object obtained through getOfferings,
onRestoreCompleted: (CustomerInfo customerInfo) {
// Optional listener. Called when a restore has been completed.
// This may be called even if no entitlements have been granted.
},
onDismiss: () {
// Dismiss the paywall, i.e. remove the view, navigate to another screen, etc.
// Will be called when a purchase succeeds.
},
contentCreator: (bottomPadding) => YourPaywall(bottomPadding),
),
),
),
);
}
Listeners
When using PaywallView
or PaywallFooterView
, you may use one of the provided listeners to react to user actions.
Available listeners at this time are:
- onPurchaseStarted
- onPurchaseCompleted
- onPurchaseError
- onRestoreCompleted
- onRestoreError
- onDismiss
Kotlin Multiplatform
How to display a fullscreen Paywall in your app
You can present a fullscreen Paywall using the Paywall
composable. You have the flexibility to decide when to call this. You could, for instance, add it to your navigation graph.
- Paywall
val options = remember {
PaywallOptions(dismissRequest = { TODO("Handle dismiss") }) {
shouldDisplayDismissButton = true
}
}
Paywall(options)
If desired, you can set shouldDisplayDismissButton
to true
in your PaywallOptions
to display a close button in the paywall. You will need to react to it using the dismissRequest
listener.
How to display a footer Paywall on your custom paywall
RevenueCatUI also has a paywall variation that can be displayed as a footer below your custom paywall. This allows you to design your paywall exactly as you want with native components while still using RevenueCat UI to handle it.
This is done by using the PaywallFooter
composable.
The footer paywall mainly consists of the following:
- Purchase button
- Package details text
- Package selection (if there are any multiple packages configured)
This is all remotely configured and RevenueCatUI handles all the intro offer eligibility and purchase logic.
- PaywallFooter
val options = remember {
PaywallOptions(dismissRequest = { TODO("Handle dismiss") }) {
shouldDisplayDismissButton = true
}
}
PaywallFooter(options) { contentPadding ->
// Your custom paywall content.
}
Listeners
When using Paywall
or PaywallFooter
, you may use one of the provided listeners to react to user actions.
Available listeners at this time are:
- onPurchaseStarted
- onPurchaseCompleted
- onPurchaseError
- onPurchaseCancelled
- onRestoreStarted
- onRestoreCompleted
- onRestoreError
Default Paywall
If you attempt to display a Paywall for an Offering that doesn't have one configured, the RevenueCatUI SDK will display a default Paywall. The default paywall displays all packages in the offering.
On iOS it uses the app's accentColor
for styling.
On Android, it uses the app's Material3
's ColorScheme
.