Flutter - Handle flow & paywall events
This guide covers event handling for purchases, restorations, product selection, and rendering. Closing the view and opening links are handled by the default flowViewDidPerformAction implementation — see our guide on handling button actions to override them or to handle custom button actions.
Flows and paywalls configured with the 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 flow or paywall. Learn how to respond to these events below.
To control or monitor processes occurring on the flow or paywall screen within your mobile app, implement the AdaptyUIFlowsEventsObserver methods and set the observer before presenting any screen:
AdaptyUI().setFlowsEventsObserver(this);Three observer methods are required — your class won’t compile without them: flowViewDidFinishPurchase, flowViewDidFinishRestore, and flowViewDidReceiveError. All other methods are optional. To detach a previously set observer, pass null to setFlowsEventsObserver.
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.
The event examples below show the properties available on each object, with illustrative values in comments.
User-generated events
View appeared
This method is invoked when the flow or paywall view is presented on the screen.
On iOS, also invoked when a user taps the web paywall button inside a paywall, and a web paywall opens in an in-app browser.
void flowViewDidAppear(AdaptyUIFlowView view) {
}View disappeared
This method is invoked when the flow or paywall view is dismissed from the screen.
On iOS, also invoked when a web paywall opened from a paywall in an in-app browser disappears from the screen.
void flowViewDidDisappear(AdaptyUIFlowView view) {
}Product selection
If a product is selected for purchase (by a user or by the system), this method will be invoked:
void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) {
}Event example (Click to expand)
void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) {
// productId is a String:
productId; // 'premium_monthly'
}Started purchase
If a user initiates the purchase process, this method will be invoked:
void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) {
}Event example (Click to expand)
void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) {
// product — AdaptyPaywallProduct:
product.vendorProductId; // 'premium_monthly'
product.localizedTitle; // 'Premium Monthly'
product.localizedDescription; // 'Premium subscription for 1 month'
product.price.amount; // 9.99 (double)
product.price.currencyCode; // 'USD'
product.price.localizedString; // '$9.99'
}Finished purchase
This method is required. It is invoked when a purchase succeeds, the user cancels their purchase, or the purchase appears to be pending:
void flowViewDidFinishPurchase(AdaptyUIFlowView 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)
void flowViewDidFinishPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product,
AdaptyPurchaseResult purchaseResult) {
// product — AdaptyPaywallProduct:
product.vendorProductId; // 'premium_monthly'
switch (purchaseResult) {
case AdaptyPurchaseResultSuccess(profile: final profile):
// profile — AdaptyProfile:
profile.accessLevels['premium']?.isActive; // true
profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
break;
case AdaptyPurchaseResultPending():
// no additional data
break;
case AdaptyPurchaseResultUserCancelled():
// no additional data
break;
}
}Unlike v3, this method has no default behavior — the view is no longer dismissed automatically after a successful purchase. Decide what happens next yourself: continue the flow or call view.dismiss(). Refer to Respond to button actions for details on dismissing a 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:
void flowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view,
AdaptyPaywallProduct? product,
AdaptyError? error) {
}Parameters:
| Parameter | Description |
|---|---|
| product | An AdaptyPaywallProduct for which the web paywall was opened. Can be null. |
| error | An AdaptyError object if the web paywall navigation failed; null if navigation was successful. |
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 flowViewDidFinishPurchase:
void flowViewDidFailPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product,
AdaptyError error) {
}Started restore
If a user initiates the restore process, this method will be invoked:
void flowViewDidStartRestore(AdaptyUIFlowView view) {
}Successful restore
This method is required. If restoring a purchase succeeds, it will be invoked:
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
}Event example (Click to expand)
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
// profile — AdaptyProfile:
profile.accessLevels['premium']?.isActive; // true
profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
profile.subscriptions['premium_monthly']?.isActive; // true
profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
}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 screen.
Failed restore
If restoring a purchase fails, this method will be invoked:
void flowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) {
}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:
void flowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) {
}View errors
This method is required. It replaces the v3 paywallViewDidFailRendering method: errors that occur during the interface rendering, as well as other view errors, are reported by calling it. Once you implement it, dismissal is up to you — we recommend dismissing the view on such errors, which is also what the SDK’s built-in default does when no observer is set:
void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) {
// log the error and dismiss the broken view
view.dismiss();
}In a normal situation, rendering errors should not occur, so if you come across one, please let us know.
Analytics events
The optional flowViewDidReceiveAnalyticEvent method is reserved for custom analytic events from a flow. Flows don’t emit these to your code yet, so you don’t need to implement it.
Handle purchases in observer mode
If you activated the SDK in Observer mode and present an Adapty-rendered flow or paywall, the SDK does not make purchases for you. When a user taps the purchase or restore button, the SDK calls your AdaptyUIObserverModeResolver instead. See Present flows in Observer mode for the full setup.
Handle system requests
The AdaptyUISystemRequestsHandler (registered via AdaptyUI().setSystemRequestsHandler(...)) is reserved for system requests from a flow: OS permission prompts (such as push notifications or camera access) and App Store review requests. Flows don’t trigger these requests yet, so you don’t need to register a handler.
If you do register one, note that handlePermission is the class’s required method — request the permission with your own code, then return AdaptyUIPermissionResult.granted() or AdaptyUIPermissionResult.denied(); handleAppReviewRequest is optional.
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.
This guide is for new Paywall Builder paywalls only which require Adapty SDK v3.0 or later.
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:
AdaptyUI().setPaywallsEventsObserver(this);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.
The event examples below show the properties available on each object, with illustrative values in comments.
User-generated events
Paywall appeared
This method is invoked when the paywall view is presented on the screen.
On iOS, also invoked when a user taps the web paywall button inside a paywall, and a web paywall opens in an in-app browser.
void paywallViewDidAppear(AdaptyUIPaywallView view) {
}Paywall disappeared
This method is invoked when the paywall view is dismissed from the screen.
On iOS, also invoked when a web paywall opened from a paywall in an in-app browser disappears from the screen.
void paywallViewDidDisappear(AdaptyUIPaywallView view) {
}Product selection
If a product is selected for purchase (by a user or by the system), this method will be invoked:
void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) {
}Event example (Click to expand)
void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) {
// productId is a String:
productId; // 'premium_monthly'
}Started purchase
If a user initiates the purchase process, this method will be invoked:
void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) {
}Event example (Click to expand)
void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) {
// product — AdaptyPaywallProduct:
product.vendorProductId; // 'premium_monthly'
product.localizedTitle; // 'Premium Monthly'
product.localizedDescription; // 'Premium subscription for 1 month'
product.price.amount; // 9.99 (double)
product.price.currencyCode; // 'USD'
product.price.localizedString; // '$9.99'
}Finished purchase
This method is invoked when a purchase succeeds, the user cancels their purchase, or the purchase appears to be pending:
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)
void paywallViewDidFinishPurchase(AdaptyUIPaywallView view,
AdaptyPaywallProduct product,
AdaptyPurchaseResult purchaseResult) {
// product — AdaptyPaywallProduct:
product.vendorProductId; // 'premium_monthly'
switch (purchaseResult) {
case AdaptyPurchaseResultSuccess(profile: final profile):
// profile — AdaptyProfile:
profile.accessLevels['premium']?.isActive; // true
profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
break;
case AdaptyPurchaseResultPending():
// no additional data
break;
case AdaptyPurchaseResultUserCancelled():
// no additional data
break;
}
}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:
void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view,
AdaptyPaywallProduct? product,
AdaptyError? error) {
}Parameters:
| Parameter | Description |
|---|---|
| product | An AdaptyPaywallProduct for which the web paywall was opened. Can be null. |
| error | An AdaptyError object if the web paywall navigation failed; null if navigation was successful. |
Event examples (Click to expand)
void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view,
AdaptyPaywallProduct? product,
AdaptyError? error) {
// product — AdaptyPaywallProduct?:
product?.vendorProductId; // 'premium_monthly'
if (error == null) {
// navigation succeeded
} else {
// error — AdaptyError:
error.code; // AdaptyErrorCode.networkFailed (2005)
error.message; // 'Network request failed'
error.detail; // platform-specific underlying error, or null
}
}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:
void paywallViewDidFailPurchase(AdaptyUIPaywallView view,
AdaptyPaywallProduct product,
AdaptyError error) {
}Event example (Click to expand)
void paywallViewDidFailPurchase(AdaptyUIPaywallView view,
AdaptyPaywallProduct product,
AdaptyError error) {
// product — AdaptyPaywallProduct:
product.vendorProductId; // 'premium_monthly'
// error — AdaptyError:
error.code; // AdaptyErrorCode.productPurchaseFailed (1006)
error.message; // 'Product purchase failed.'
error.detail; // platform-specific underlying error, or null
}Started restore
If a user initiates the restore process, this method will be invoked:
void paywallViewDidStartRestore(AdaptyUIPaywallView view) {
}Successful restore
If restoring a purchase succeeds, this method will be invoked:
void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) {
}Event example (Click to expand)
void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) {
// profile — AdaptyProfile:
profile.accessLevels['premium']?.isActive; // true
profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
profile.subscriptions['premium_monthly']?.isActive; // true
profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30)
}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:
void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) {
}Event example (Click to expand)
void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) {
// error — AdaptyError:
error.code; // AdaptyErrorCode.receiveRestoredTransactionsFailed (1011)
error.message; // 'Error occurred in the process of restoring purchases.'
error.detail; // platform-specific underlying error, or null
}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:
void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) {
}Event example (Click to expand)
void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) {
// error — AdaptyError:
error.code; // AdaptyErrorCode.productRequestFailed (1002)
error.message; // 'Unable to fetch available In-App Purchase products at the moment.'
error.detail; // platform-specific underlying error, or null
}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.
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)
void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) {
// error — AdaptyError:
error.code; // AdaptyErrorCode.jsException (4105)
error.message; // 'An exception was thrown from JS during AdaptyUI flow execution.'
error.detail; // platform-specific underlying error, or null
// Default behavior: view.dismiss()
}In a normal situation, such errors should not occur, so if you come across one, please let us know.