---
id: "tools/paywalls-legacy/displaying-paywalls"
title: "Displaying Paywalls"
description: "These docs refer to our legacy paywalls."
permalink: "/docs/tools/paywalls-legacy/displaying-paywalls"
slug: "displaying-paywalls"
version: "current"
original_source: "docs/tools/paywalls-legacy/displaying-paywalls.mdx"
---

> **AI agents:** This is the Markdown version of a RevenueCat documentation page. For the complete documentation index, see [llms.txt](https://www.revenuecat.com/docs/llms.txt).

:::warning\[Legacy Paywalls]
These docs refer to our legacy paywalls.

To learn more about our new paywall builder, [click here](https://www.revenuecat.com/docs/tools/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` or `PaywallViewController`

**Entitlement**

```swift
import SwiftUI

import RevenueCat
import RevenueCatUI

struct App: View {
    var body: some View {
        ContentView()
            .presentPaywallIfNeeded(
                requiredEntitlementIdentifier: Constants.ENTITLEMENT_ID,
                purchaseCompleted: { customerInfo in
                    print("Purchase completed: \(customerInfo.entitlements)")
                },
                restoreCompleted: { customerInfo in
                    // Paywall will be dismissed automatically if the entitlement is now active.
                    print("Purchases restored: \(customerInfo.entitlements)")
                }
            )
    }
}
```

**Custom Logic**

```swift
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: { customerInfo in
                // Paywall will be dismissed automatically if "pro" is now active.
                print("Purchases restored: \(customerInfo.entitlements)")
            }
    }
}
```

**Manually**

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

**Manually (UIKit)**

```swift
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) {

    }

}
```

**Manually (UIKit and Objective-C)**

```objectivec

#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`](https://developer.apple.com/documentation/swiftui/view/tint\(_:\)-23xyq): `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.

:::info\[New view modifier for footer Paywalls]
In iOS SDK versions 5.16.0+, which support Paywalls v2, the original `.paywallFooter()` method has been deprecated and replaced with a new `.originalTemplatePaywallFooter()` method. The behavior is identical, but its important to keep in mind that footer Paywalls are only supported on original Paywalls, and therefore if your app uses them it should only use Offerings with original Paywalls.
:::

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**

```swift
import SwiftUI

import RevenueCat
import RevenueCatUI

struct YourPaywall: View {

    var body: some View {
        ScrollView {
            // Your custom paywall design content
        }
        .paywallFooter()
    }

}
```

**Specific Offering**

```swift
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**

```swift
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")
            }
    }
}
```

**Manual PaywallFontProvider**

```swift
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`, or `PaywallActivityLauncher`

**Entitlement**

```kotlin
@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()
    )
}
```

**Custom Logic**

```kotlin
@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()
            )
        }
    }
}
```

**Manually**

```kotlin
@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()
            )
        }
    }
}
```

**Manually (Activity)**

```kotlin
@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) {}
}
```

**Manually (Activity) - Java**

```java
@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**

```kotlin
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
private fun PaywallScreen(dismissRequest: () -> Unit) {
    PaywallFooter(
        options = PaywallOptions.Builder(dismissRequest).build()
    ) {
        CustomPaywallContent()        
    }
}
```

**Specific Offering**

```kotlin
@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**

```kotlin
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@Composable
fun MyComposable() {
    PaywallDialog(
        PaywallDialogOptions.Builder { /* on dismiss */ }
            .setFontProvider(CustomFontProvider(myFontFamily))
            .build()
    )
}
```

**Compose font per style**

```kotlin
@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()
    )
}
```

**Activity single resource font**

```kotlin
@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)
        )
    }
}
```

**Activity single Google font**

```kotlin
@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**

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

**RevenueCatUI.Paywall**

```jsx
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 
          options={{ displayCloseButton: true }}
          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
            displayCloseButton: true
          }}
          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

:::warning
`PaywallFooterContainerView` is not supported when [React Native's New Architecture](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here) is enabled.
Please see this [GitHub issue](https://github.com/RevenueCat/react-native-purchases/issues/1158#issuecomment-2613000475) for more information.
:::

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.

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

:::warning
`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**

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

**Footer**

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

**presentPaywall**

```jsx
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**

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

**PaywallView**

```dart
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.
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: Center(
        child: PaywallView(
          offering: offering, // Optional Offering object obtained through getOfferings
          displayCloseButton: true,
          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.

```dart
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.
@override
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.

```kotlin
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.

```kotlin
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`.
