Android - Handle paywall events
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. For presenting paywalls in Adapty SDK v2 designed with legacy Paywall Builder, see Handle paywall events designed with legacy Paywall Builder.
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.
If you need to control or monitor the processes that take place on the purchase screen, implement the AdaptyUiEventListener
methods.
If you would like to leave the default behavior in some cases, you can extend AdaptyUiDefaultEventListener
and override only those methods you want to change.
Below are the defaults from AdaptyUiDefaultEventListener
.
User-generated events
Product selection
If a product is selected for purchase (by a user or by the system), this method will be invoked:
public override fun onProductSelected(
product: AdaptyPaywallProduct,
context: Context,
) {}
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"
}
}
Started purchase
If a user initiates the purchase process, this method will be invoked:
public override fun onPurchaseStarted(
product: AdaptyPaywallProduct,
context: Context,
) {}
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"
}
}
The method will not be invoked in Observer mode. Refer to the Android - Present Paywall Builder paywalls in Observer mode topic for details.
Successful, canceled, or pending purchase
If Adapty.makePurchase()
succeeds, this method will be invoked:
public override fun onPurchaseFinished(
purchaseResult: AdaptyPurchaseResult,
product: AdaptyPaywallProduct,
context: Context,
) {
if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
context.getActivityOrNull()?.onBackPressed()
}
Event examples (Click to expand)
// Successful purchase
{
"purchaseResult": {
"type": "Success",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Cancelled purchase
{
"purchaseResult": {
"type": "UserCanceled"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Pending purchase
{
"purchaseResult": {
"type": "Pending"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
We recommend dismissing the screen in that case.
The method will not be invoked in Observer mode. Refer to the Android - Present Paywall Builder paywalls in Observer mode topic for details.
Failed purchase
If Adapty.makePurchase()
fails, this method will be invoked:
public override fun onPurchaseFailure(
error: AdaptyError,
product: AdaptyPaywallProduct,
context: Context,
) {}
Event example (Click to expand)
{
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
The method will not be invoked in Observer mode. Refer to the Android - Present Paywall Builder paywalls in Observer mode topic for details.
Successful restore
If Adapty.restorePurchases()
succeeds, this method will be invoked:
public override fun onRestoreSuccess(
profile: AdaptyProfile,
context: Context,
) {}
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.
Failed restore
If Adapty.restorePurchases()
fails, this method will be invoked:
public override fun onRestoreFailure(
error: AdaptyError,
context: Context,
) {}
Event example (Click to expand)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
Upgrade subscription
- SDK version 3.10.0 or later
- SDK version earlier than 3.10.0
When a user attempts to purchase a new subscription while another subscription is active, you can control how the new purchase should be handled by overriding this method. You have two options:
- Replace the current subscription with the new one:
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
.build()
)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
- Keep both subscriptions (add the new one separately):
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
If you don't override this method, the default behavior is to keep both subscriptions active (equivalent to using AdaptyPurchaseParameters.Empty
).
You can also set additional purchase parameters if needed:
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
.withOfferPersonalized(true) // optional - if using personalized pricing
.build()
If a new subscription is purchased while another is still active, override this method to replace the current one with the new one. If the active subscription should remain active and the new one is added separately, call onSubscriptionUpdateParamsReceived(null)
:
public override fun onAwaitingSubscriptionUpdateParams(
product: AdaptyPaywallProduct,
context: Context,
onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_yearly",
"localizedTitle": "Premium Yearly",
"localizedDescription": "Premium subscription for 1 year",
"localizedPrice": "$99.99",
"price": 99.99,
"currencyCode": "USD"
},
"subscriptionUpdateParams": {
"replacementMode": "with_time_proration"
}
}
Data fetching and rendering
Product loading errors
If you don't pass the products 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:
public override fun onLoadingProductsFailure(
error: AdaptyError,
context: Context,
): Boolean = false
Event example (Click to expand)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}
If you return true
, AdaptyUI will repeat the request in 2 seconds.
Rendering errors
If an error occurs during the interface rendering, it will be reported by calling this method:
public override fun onRenderingError(
error: AdaptyError,
context: Context,
) {}
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.