---
title: "Display flows & paywalls - Flutter"
description: "Present flows and paywalls in Flutter apps using Adapty's monetization features."
---

If you've designed a flow or paywall using the Flow Builder or Paywall Builder, you don't need to worry about rendering it in your mobile app code to display it to the user. Such a flow or paywall contains both what should be shown and how it should be shown.

:::warning

This guide is for flows and Paywall Builder paywalls. For presenting **Remote config paywalls**, see [Render paywall designed by remote config](present-remote-config-paywalls-flutter).

:::

Adapty Flutter SDK provides two ways to present flows and paywalls:

- **Standalone screen**

- **Embedded widget**

## Present as standalone screen

To display a flow or paywall as a standalone screen, use the `view.present()` method on the `view` created by the [`createFlowView`](flutter-get-pb-paywalls#fetch-the-view-configuration) method. Each `view` can only be presented once: after you dismiss it, the view is released from memory. If you need to display the flow or paywall again, call `createFlowView` one more time to create a new `view` instance.

```dart showLineNumbers title="Flutter"
try {
  await view.present();
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```
:::tip

Want to see a real-world example of how Adapty SDK is integrated into a mobile app? Check out our [sample apps](sample-apps), which demonstrate the full setup, including displaying paywalls, making purchases, and other basic functionality.

:::

### Dismiss the flow or paywall

When you need to programmatically close the flow or paywall, use the `dismiss()` method:

```dart showLineNumbers title="Flutter"
try {
  await view.dismiss();
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```

:::note
Dismissing a view releases it from memory — a dismissed view can no longer be re-presented. Create a new one with `createFlowView` instead.
:::

### Show dialog

Use this method instead of native alert dialogs when a flow or paywall view is presented on Android. On Android, regular alerts appear behind the view, which makes them invisible to users. This method ensures proper dialog presentation above the flow or paywall on all platforms.

```dart showLineNumbers title="Flutter"
try {
  final action = await view.showDialog(
    title: 'Close paywall?',
    content: 'You will lose access to exclusive offers.',
    primaryActionTitle: 'Stay',
    secondaryActionTitle: 'Close',
  );
  
  if (action == AdaptyUIDialogActionType.secondary) {
    // User confirmed - close the paywall
    await view.dismiss();
  }
  // If primary - do nothing, user stays
} catch (e) {
  // handle error
}
```

### Configure iOS presentation style

Configure how the flow or paywall is presented on iOS by passing the `iosPresentationStyle` parameter to the `present()` method. The parameter accepts `AdaptyUIIOSPresentationStyle.fullScreen` (default) or `AdaptyUIIOSPresentationStyle.pageSheet` values.

```dart showLineNumbers
try {
  await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet);
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```

## Embed in widget hierarchy

To embed a flow or paywall within your existing widget tree, use the `AdaptyUIFlowPlatformView` widget directly in your Flutter widget hierarchy.

```dart showLineNumbers title="Flutter"
AdaptyUIFlowPlatformView(
  flow: flow, // The flow object you fetched
  onDidAppear: (view) {
  },
  onDidDisappear: (view) {
  },
  onDidPerformAction: (view, action) {
  },
  onDidSelectProduct: (view, productId) {
  },
  onDidStartPurchase: (view, product) {
  },
  onDidFinishPurchase: (view, product, purchaseResult) {
  },
  onDidFailPurchase: (view, product, error) {
  },
  onDidStartRestore: (view) {
  },
  onDidFinishRestore: (view, profile) {
  },
  onDidFailRestore: (view, error) {
  },
  onDidReceiveError: (view, error) {
  },
  onDidFailLoadingProducts: (view, error) {
  },
  onDidFinishWebPaymentNavigation: (view, product, error) {
  },
)
```

:::note 
For Android platform view to work, ensure your `MainActivity` extends `FlutterFragmentActivity`:

```kotlin showLineNumbers title="Kotlin"
class MainActivity : FlutterFragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}
```
:::

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

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.

:::warning

This guide is for **new Paywall Builder paywalls** only which require SDK v3.2.0 or later. The process for presenting paywalls differs for paywalls designed with different versions of Paywall Builde and remote config paywalls.

- For presenting **Remote config paywalls**, see [Render paywall designed by remote config](present-remote-config-paywalls-flutter).

:::

Adapty Flutter SDK provides two ways to present paywalls:

- **Standalone screen**

- **Embedded widget**

## Present as standalone screen

To display a paywall as a standalone screen, use the `view.present()` method on the `view` created by the [`createPaywallView`](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 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. 

:::warning

Reusing the same `view` without recreating it may result in an `AdaptyUIError.viewAlreadyPresented` error.
:::

```dart showLineNumbers title="Flutter"
try {
  await view.present();
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```
:::tip

Want to see a real-world example of how Adapty SDK is integrated into a mobile app? Check out our [sample apps](sample-apps), which demonstrate the full setup, including displaying paywalls, making purchases, and other basic functionality.

:::

### Dismiss the paywall

When you need to programmatically close the paywall, use the `dismiss()` method:

```dart showLineNumbers title="Flutter"
try {
  await view.dismiss();
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```

### 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.

```dart showLineNumbers title="Flutter"
try {
  final action = await view.showDialog(
    title: 'Close paywall?',
    content: 'You will lose access to exclusive offers.',
    primaryActionTitle: 'Stay',
    secondaryActionTitle: 'Close',
  );
  
  if (action == AdaptyUIDialogActionType.secondary) {
    // User confirmed - close the paywall
    await view.dismiss();
  }
  // If primary - do nothing, user stays
} catch (e) {
  // 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 `AdaptyUIIOSPresentationStyle.fullScreen` (default) or `AdaptyUIIOSPresentationStyle.pageSheet` values.

```dart showLineNumbers
try {
  await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet);
} on AdaptyError catch (e) {
  // handle the error
} catch (e) {
  // handle the error
}
```

## Embed in widget hierarchy

To embed a paywall within your existing widget tree, use the `AdaptyUIPaywallPlatformView` widget directly in your Flutter widget hierarchy.

```dart showLineNumbers title="Flutter"
AdaptyUIPaywallPlatformView(
  paywall: paywall, // The paywall object you fetched
  onDidAppear: (view) {
  },
  onDidDisappear: (view) {
  },
  onDidPerformAction: (view, action) {
  },
  onDidSelectProduct: (view, productId) {
  },
  onDidStartPurchase: (view, product) {
  },
  onDidFinishPurchase: (view, product, purchaseResult) {
  },
  onDidFailPurchase: (view, product, error) {
  },
  onDidStartRestore: (view) {
  },
  onDidFinishRestore: (view, profile) {
  },
  onDidFailRestore: (view, error) {
  },
  onDidFailRendering: (view, error) {
  },
  onDidFailLoadingProducts: (view, error) {
  },
  onDidFinishWebPaymentNavigation: (view, product, error) {
  },
)
```

:::note 
For Android platform view to work, ensure your `MainActivity` extends `FlutterFragmentActivity`:

```kotlin showLineNumbers title="Kotlin"
class MainActivity : FlutterFragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}
```
:::

---