Manejar eventos de flow y paywall - Kotlin Multiplatform
Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También debes implementar el manejo de botones (cerrar el flow, abrir enlaces, etc.). Consulta nuestra guía sobre el manejo de acciones de flow para más detalles.
Los flows y paywalls configurados con el Flow Builder o el Paywall Builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Esos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras. Aprende a responder a estos eventos a continuación.
Para controlar o monitorear los procesos que ocurren en la pantalla del flow dentro de tu app móvil, implementa los métodos de la interfaz AdaptyUIFlowsEventsObserver y registra tu observador con AdaptyUI.setFlowsEventsObserver(). Algunos métodos tienen implementaciones predeterminadas que gestionan automáticamente los escenarios más comunes, así que sobrescribe solo los métodos que quieras modificar:
AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
// override only the methods you want to change
})Estos métodos son donde añades tu lógica personalizada para responder a los eventos del flow. Puedes usar view.dismiss() para cerrar el flow, o implementar cualquier otro comportamiento personalizado que necesites. Ten en cuenta que dismiss() es una función suspend — dentro de un callback, ejecútala en el mainUiScope del observer: mainUiScope.launch { view.dismiss() }.
Eventos generados por el usuario
Aparición y desaparición del flow
Cuando un flow aparece o desaparece, se invocarán estos métodos:
override fun flowViewDidAppear(view: AdaptyUIFlowView) {
// Handle flow appearance
// You can track analytics or update UI here
}
override fun flowViewDidDisappear(view: AdaptyUIFlowView) {
// Handle flow disappearance
// You can track analytics or update UI here
}- En iOS,
flowViewDidAppeartambién se invoca cuando el usuario toca el botón de web paywall dentro de un flow, y se abre un web paywall en un navegador in-app. - En iOS,
flowViewDidDisappeartambién se invoca cuando un web paywall abierto desde un flow en un navegador in-app desaparece de la pantalla.
Ejemplos de eventos (haz clic para expandir)
// Flow appeared
{
// No additional data
}
// Flow disappeared
{
// No additional data
}Selección de producto
Si el usuario selecciona un producto para comprar, se invocará este método:
override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) {
// Handle product selection
// You can update UI or track analytics here
}Ejemplo de evento (haz clic para expandir)
{
"productId": "premium_monthly"
}Compra iniciada
Si el usuario inicia el proceso de compra, se invocará este método:
override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) {
// Handle purchase start
// You can show loading indicators or track analytics here
}En el modo observador, las compras iniciadas desde un flow se entregan a tu AdaptyUIObserverModeResolver en lugar de aquí.
Ejemplo de evento (haz clic para expandir)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}Compra exitosa, cancelada o pendiente
Este método se invoca cuando finaliza una compra. Por defecto no hace nada — el flow permanece abierto después de la compra hasta que lo cierres tú, así que llama a view.dismiss() una vez que el usuario obtenga acceso:
override fun flowViewDidFinishPurchase(
view: AdaptyUIFlowView,
product: AdaptyPaywallProduct,
purchaseResult: AdaptyPurchaseResult
) {
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
// Check if user has access to premium features
if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) {
mainUiScope.launch { view.dismiss() }
}
}
AdaptyPurchaseResult.Pending -> {
// Handle pending purchase (e.g., user will pay offline with cash)
}
AdaptyPurchaseResult.UserCanceled -> {
// Handle user cancellation
}
}
}Ejemplos de eventos (Haz clic para expandir)
// 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": "Success",
"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": "Pending"
}
}
// User canceled purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "UserCanceled"
}
}Recomendamos cerrar la pantalla del flow en caso de compra exitosa.
Compra fallida
Si una compra falla debido a un error, se invocará este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos no válidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan flowViewDidFinishPurchase con un resultado cancelado en su lugar, y los pagos pendientes no activan este método.
override fun flowViewDidFailPurchase(
view: AdaptyUIFlowView,
product: AdaptyPaywallProduct,
error: AdaptyError
) {
// Add your purchase failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (Clic para expandir)
{
"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"
}
}
}Restauración iniciada
Si un usuario inicia el proceso de restauración, se invocará este método:
override fun flowViewDidStartRestore(view: AdaptyUIFlowView) {
// Handle restore start
// You can show loading indicators or track analytics here
}Restauración exitosa
Si la restauración de una compra se realiza correctamente, se invocará este método. Por defecto, no hace nada: el flow permanece abierto después de la restauración hasta que lo cierras:
override fun flowViewDidFinishRestore(view: AdaptyUIFlowView, profile: AdaptyProfile) {
// Add your successful restore handling logic here
// For example: show success message, update UI, or dismiss the flow
// Check if user has access to premium features
if (profile.accessLevels["premium"]?.isActive == true) {
mainUiScope.launch { view.dismiss() }
}
}Ejemplo de evento (haz clic para expandir)
{
"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"
}
]
}
}Te recomendamos cerrar la pantalla si el usuario tiene el accessLevel requerido. Consulta el tema Estado de la suscripción para aprender cómo comprobarlo.
Restauración fallida
Si Adapty.restorePurchases() falla, se invocará este método:
override fun flowViewDidFailRestore(view: AdaptyUIFlowView, error: AdaptyError) {
// Add your restore failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (Haz clic para expandir)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}Finalización de la navegación de pago web
Si un usuario inicia el proceso de compra mediante un paywall web, se invocará este método:
override fun flowViewDidFinishWebPaymentNavigation(
view: AdaptyUIFlowView,
product: AdaptyPaywallProduct?,
error: AdaptyError?
) {
if (error != null) {
// Handle web payment navigation error
} else {
// Handle successful web payment navigation
}
}Ejemplos de eventos (haz clic para expandir)
// Successful web payment 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 web payment navigation
{
"product": null,
"error": {
"code": "web_payment_failed",
"message": "Web payment navigation failed",
"details": {
"underlyingError": "Network connection error"
}
}
}Obtención y renderizado de datos
Errores al cargar productos
Si no proporcionas los productos durante la inicialización, AdaptyUI los recuperará del servidor por sí solo. Si esta operación falla, AdaptyUI notificará el error llamando a este método:
override fun flowViewDidFailLoadingProducts(view: AdaptyUIFlowView, error: AdaptyError) {
// Add your product loading failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (haz clic para expandir)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}Errores de renderizado y en tiempo de ejecución
Si se produce un error durante el renderizado de la interfaz o cualquier otro error en tiempo de ejecución que no sea de compra, este método lo notificará. Por defecto, el flow se cierra al producirse un error — sobreescribe el método para mantenerlo abierto o añadir tu propia gestión:
override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {
// Handle the error
// The default implementation dismisses the flow;
// once you override this method, dismissal is up to you
}Ejemplo de evento (haz clic para expandir)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render flow interface",
"details": {
"underlyingError": "Invalid flow configuration"
}
}
}En circunstancias normales, estos errores no deberían producirse, por lo que si te encuentras con alguno, por favor, comunícanoslo.
Eventos de analíticas
El callback flowViewDidReceiveAnalyticEvent está reservado para eventos analíticos personalizados de un flow. Los flows aún no emiten estos eventos a tu código, por lo que no necesitas implementarlo:
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String
) {
// Reserved for custom analytic events from a flow
}Navegación
Botón Atrás del sistema Android
Por defecto, un flow no puede cerrarse con el botón Atrás del sistema Android ni con el gesto de retroceso — la implementación predeterminada de flowViewDidPerformAction solo cierra el flow al recibir CloseAction e ignora AndroidSystemBackAction, de modo que el usuario abandona el flow por la ruta que tú definas, como un botón Close o una acción on_device_back en el builder. Si quieres que el botón Atrás del sistema cierre el flow, gestiona la acción tú mismo:
override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
when (action) {
is AdaptyUIAction.CloseAction ->
mainUiScope.launch { view.dismiss() } // default behavior
is AdaptyUIAction.AndroidSystemBackAction ->
mainUiScope.launch { view.dismiss() } // not handled by default
is AdaptyUIAction.OpenUrlAction ->
AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior
else -> Unit
}
}Consulta la guía sobre cómo gestionar las acciones de flow para ver la lista completa de acciones.
Los paywalls configurados con el Paywall Builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación encontrarás cómo responder a estos eventos.
Esta guía es solo para paywalls creados con el nuevo Paywall Builder.
Para controlar o supervisar los procesos que ocurren en la pantalla del paywall dentro de tu aplicación móvil, implementa los métodos de la interfaz AdaptyUIPaywallsEventsObserver. Algunos métodos tienen implementaciones predeterminadas que gestionan automáticamente los escenarios más comunes.
En estos métodos es donde añades tu lógica personalizada para responder a los eventos del paywall. Puedes usar view.dismiss() para cerrar el paywall, o implementar cualquier otro comportamiento personalizado que necesites.
Eventos generados por el usuario
Aparición y desaparición del paywall
Cuando un paywall aparece o desaparece, se invocarán estos métodos:
override fun paywallViewDidAppear(view: AdaptyUIPaywallView) {
// Handle paywall appearance
// You can track analytics or update UI here
}
override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) {
// Handle paywall disappearance
// You can track analytics or update UI here
}- En iOS,
paywallViewDidAppeartambién se invoca cuando el usuario pulsa el botón de paywall web dentro de un paywall y se abre un paywall web en un navegador in-app. - En iOS,
paywallViewDidDisappeartambién se invoca cuando un paywall web abierto desde un paywall en un navegador in-app desaparece de la pantalla.
Ejemplos de eventos (haz clic para expandir)
// Paywall appeared
{
// No additional data
}
// Paywall disappeared
{
// No additional data
}Selección de producto
Si un usuario selecciona un producto para comprarlo, se invocará este método:
override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
// Handle product selection
// You can update UI or track analytics here
}Ejemplo de evento (haz clic para expandir)
{
"productId": "premium_monthly"
}Inicio de compra
Si un usuario inicia el proceso de compra, se invocará este método:
override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
// Handle purchase start
// You can show loading indicators or track analytics here
}Ejemplo de evento (Haz clic para expandir)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}Compra exitosa, cancelada o pendiente
Si una compra se realiza con éxito, se invocará este método. Por defecto, cierra automáticamente el paywall a menos que el usuario haya cancelado la compra:
override fun paywallViewDidFinishPurchase(
view: AdaptyUIPaywallView,
product: AdaptyPaywallProduct,
purchaseResult: AdaptyPurchaseResult
) {
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
// Check if user has access to premium features
if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) {
view.dismiss()
}
}
AdaptyPurchaseResult.Pending -> {
// Handle pending purchase (e.g., user will pay offline with cash)
}
AdaptyPurchaseResult.UserCanceled -> {
// Handle user cancellation
}
}
}Ejemplos de eventos (Haz clic para expandir)
// 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": "Success",
"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": "Pending"
}
}
// User canceled purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "UserCanceled"
}
}Recomendamos cerrar la pantalla del paywall en caso de compra exitosa.
Compra fallida
Si una compra falla debido a un error, se invocará este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos no válidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan paywallViewDidFinishPurchase con un resultado de cancelación, y los pagos pendientes no activan este método.
override fun paywallViewDidFailPurchase(
view: AdaptyUIPaywallView,
product: AdaptyPaywallProduct,
error: AdaptyError
) {
// Add your purchase failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (clic para expandir)
{
"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"
}
}
}Restauración iniciada
Si un usuario inicia el proceso de restauración, se invocará este método:
override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) {
// Handle restore start
// You can show loading indicators or track analytics here
}Restauración exitosa
Si la restauración de una compra se realiza correctamente, se invocará este método:
override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) {
// Add your successful restore handling logic here
// For example: show success message, update UI, or dismiss paywall
// Check if user has access to premium features
if (profile.accessLevels["premium"]?.isActive == true) {
view.dismiss()
}
}Ejemplo de evento (Haz clic para expandir)
{
"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"
}
]
}
}Te recomendamos cerrar la pantalla si el usuario tiene el accessLevel requerido. Consulta el artículo Estado de suscripción para saber cómo comprobarlo.
Error al restaurar
Si Adapty.restorePurchases() falla, se invocará este método:
override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your restore failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (Haz clic para expandir)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}Finalización de la navegación del pago web
Si un usuario inicia el proceso de compra mediante un paywall web, se invocará este método:
override fun paywallViewDidFinishWebPaymentNavigation(
view: AdaptyUIPaywallView,
product: AdaptyPaywallProduct?,
error: AdaptyError?
) {
if (error != null) {
// Handle web payment navigation error
} else {
// Handle successful web payment navigation
}
}Ejemplos de eventos (Haz clic para expandir)
// Successful web payment 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 web payment navigation
{
"product": null,
"error": {
"code": "web_payment_failed",
"message": "Web payment navigation failed",
"details": {
"underlyingError": "Network connection error"
}
}
}Obtención y renderizado de datos
Errores de carga de productos
Si no pasas los productos durante la inicialización, AdaptyUI los recuperará del servidor por su cuenta. Si esta operación falla, AdaptyUI notificará el error llamando a este método:
override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your product loading failure handling logic here
// For example: show error message, retry option, or custom error handling
}Ejemplo de evento (Haz clic para expandir)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}Errores de renderizado
Si se produce un error durante el renderizado de la interfaz, este método lo notificará:
override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {
// Handle rendering error
// In a normal situation, such errors should not occur
// If you come across one, please let us know
}Ejemplo de evento (Haz clic para expandir)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}En una situación normal, estos errores no deberían ocurrir, así que si te encuentras con uno, por favor, comunícanoslo.