Skip to main content

Flutter - Handle paywall events

important

This guide covers event handling for purchases, restorations, product selection, and paywall rendering. You must also implement button handling (closing paywall, opening links, etc.). See our guide on handling button actions for details.

Paywalls configured with the Paywall Builder don't need extra code to make and restore purchases. However, they generate some events that your app can respond to. Those events include button presses (close buttons, URLs, product selections, and so on) as well as notifications on purchase-related actions taken on the paywall. Learn how to respond to these events below.

warning

This guide is for new Paywall Builder paywalls only which require Adapty SDK v3.0 or later. For presenting paywalls in Adapty SDK v2 designed with legacy Paywall Builder, see Flutter - Handle paywall events designed with legacy Paywall Builder.

To control or monitor processes occurring on the paywall screen within your mobile app, implement the AdaptyUIPaywallsEventsObserver methods and set the observer before presenting any screen:

Flutter
AdaptyUI().setPaywallsEventsObserver(this);
tip

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

User-generated events

Product selection

If a product is selected for purchase (by a user or by the system), this method will be invoked:

Flutter
void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) {
}
Event example (Click to expand)
{
"productId": "premium_monthly"
}

Started purchase

If a user initiates the purchase process, this method will be invoked:

Flutter
void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) {
}
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}

Finished purchase

This method is invoked when a purchase succeeds, the user cancels their purchase, or the purchase appears to be pending:

Flutter
void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, 
AdaptyPaywallProduct product,
AdaptyPurchaseResult purchaseResult) {
switch (purchaseResult) {
case AdaptyPurchaseResultSuccess(profile: final profile):
// successful purchase
break;
case AdaptyPurchaseResultPending():
// purchase is pending
break;
case AdaptyPurchaseResultUserCancelled():
// user cancelled the purchase
break;
default:
break;
}
}
Event examples (Click to expand)
// Successful purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "AdaptyPurchaseResultSuccess",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
}
}

// Pending purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "AdaptyPurchaseResultPending"
}
}

// User cancelled purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "AdaptyPurchaseResultUserCancelled"
}
}

We recommend dismissing the screen in that case. Refer to Respond to button actions for details on dismissing a paywall screen.

Finished web payment navigation

This method is invoked after an attempt to open a web paywall for a specific product. This includes both successful and failed navigation attempts:

Flutter
void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, 
AdaptyPaywallProduct? product,
AdaptyError? error) {
}

Parameters:

ParameterDescription
productAn AdaptyPaywallProduct for which the web paywall was opened. Can be null.
errorAn AdaptyError object if the web paywall navigation failed; null if navigation was successful.
Event examples (Click to expand)
// Successful navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": null
}

// Failed navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "web_navigation_failed",
"message": "Failed to open web paywall",
"details": {
"underlyingError": "Browser unavailable"
}
}
}

Failed purchase

This method is invoked when a purchase fails (for example, due to payment issues or network errors). It does not fire for user-initiated cancellations or pending transactions—those are handled by paywallViewDidFinishPurchase:

Flutter
void paywallViewDidFailPurchase(AdaptyUIPaywallView view, 
AdaptyPaywallProduct product,
AdaptyError error) {
}
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
}
}

Successful restore

If restoring a purchase succeeds, this method will be invoked:

Flutter
void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) {
}
Event example (Click to expand)
{
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
},
"subscriptions": [
{
"vendorProductId": "premium_monthly",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
]
}
}

We recommend dismissing the screen if the user has the required accessLevel. Refer to the Subscription status topic to learn how to check it and to Respond to button actions topic to learn how to dismiss a paywall screen.

Failed restore

If restoring a purchase fails, this method will be invoked:

Flutter
void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) {
}
Event example (Click to expand)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}

Data fetching and rendering

Product loading errors

If you don't pass the product array during the initialization, AdaptyUI will retrieve the necessary objects from the server by itself. If this operation fails, AdaptyUI will report the error by invoking this method:

Flutter
void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) {
}
Event example (Click to expand)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}

Rendering errors

If an error occurs during the interface rendering, it will be reported by calling this method. By default (since v3.15.2), the paywall is automatically dismissed when a rendering error occurs, but you can override this behavior if needed.

Flutter
void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) {
// Default behavior: view.dismiss()
// Override with custom logic if needed, for example:
// - Log the error
// - Show an error message to the user
}
Event example (Click to expand)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}

In a normal situation, such errors should not occur, so if you come across one, please let us know.