:::important
En un proyecto de Kotlin Multiplatform, aplica estos cambios en el módulo de la aplicación Android (el que genera el APK/AAB), por ejemplo, `androidApp` o `app`:
- Manifest: `androidApp/src/main/AndroidManifest.xml`
- XML de reglas de copia de seguridad: `androidApp/src/main/res/xml/`
:::
#### Las compras fallan al volver desde otra app en Android \{#purchases-fail-after-returning-from-another-app-in-android\}
Si la Activity que inicia el flujo de compra usa un `launchMode` no predeterminado, Android puede recrearla o reutilizarla de forma incorrecta cuando el usuario regresa de Google Play, una app bancaria o un navegador. Esto puede hacer que el resultado de la compra se pierda o se trate como cancelada.
Para que las compras funcionen correctamente, usa solo los modos `standard` o `singleTop` en la Activity que inicia el flujo de compra, y evita cualquier otro modo.
En tu `AndroidManifest.xml`, asegúrate de que la Activity que inicia el flujo de compra esté configurada como `standard` o `singleTop`:
```xml
```
---
# File: kmp-quickstart-paywalls
---
---
title: "Habilitar compras usando paywalls en el SDK de Kotlin Multiplatform"
description: "Guía de inicio rápido para configurar Adapty en la gestión de suscripciones in-app."
---
Para habilitar las compras in-app, necesitas entender tres conceptos clave:
- [**Productos**](product) – cualquier cosa que los usuarios puedan comprar (suscripciones, consumibles, acceso de por vida)
- [**Paywalls**](paywalls) son configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar ofertas, precios y combinaciones de productos sin tocar el código de tu app.
- [**Placements**](placements) – dónde y cuándo muestras los paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras los paywalls para los placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos paywalls a distintos usuarios.
Adapty te ofrece tres formas de habilitar compras en tu app. Elige la que mejor se adapte a los requisitos de tu aplicación:
| Implementación | Complejidad | Cuándo usarla |
|---------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Adapty Paywall Builder | ✅ Fácil | [Creas un paywall completo y listo para compras en el editor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones en segundo plano. |
| Paywalls creados manualmente | 🟡 Media | Implementas la UI de tu paywall en el código de tu app, pero sigues obteniendo el objeto paywall de Adapty para mantener flexibilidad en las ofertas de productos. Consulta la [guía](kmp-quickstart-manual). |
| Modo observer | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observer tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). |
:::important
**Los pasos que se muestran a continuación explican cómo implementar un paywall creado en el Adapty Paywall Builder.**
Si no quieres usar el Paywall Builder, consulta la [guía para gestionar compras en paywalls creados manualmente](kmp-making-purchases).
:::
Para mostrar un paywall creado en el Adapty Paywall Builder, en el código de tu app solo necesitas:
1. **Obtener el paywall**: Obtén el paywall desde Adapty.
2. **Mostrar el paywall y Adapty gestionará las compras por ti**: Muestra el contenedor del paywall que obtuviste en tu app.
3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con el paywall con la respuesta de tu app a ellas. Por ejemplo, abrir enlaces o cerrar el paywall cuando los usuarios pulsan botones.
## Antes de empezar \{#before-you-start\}
Antes de comenzar, completa estos pasos:
1. Conecta tu app al [App Store](initial_ios) y/o [Google Play](initial-android) en el Adapty Dashboard.
2. [Crea tus productos](create-product) en Adapty.
3. [Crea un paywall y añade productos a él](create-paywall).
4. [Crea un placement y añade tu paywall a él](create-placement).
5. [Instala y activa el SDK de Adapty](sdk-installation-kotlin-multiplatform) en el código de tu app.
:::tip
La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando la [CLI para desarrolladores](developer-cli-quickstart).
:::
## 1. Obtener el paywall \{#1-get-the-paywall\}
Tus paywalls están asociados con placements configurados en el dashboard. Los placements te permiten ejecutar diferentes paywalls para distintas audiencias o realizar [pruebas A/B](ab-tests).
Para obtener un paywall creado en el Adapty Paywall Builder, necesitas:
1. Obtener el objeto `paywall` por el ID del [placement](placements) usando el método `getPaywall` y comprobar si es un paywall creado en el builder.
2. Obtener la configuración de vista del paywall usando el método `createPaywallView`. La configuración de vista contiene los elementos de UI y el estilo necesarios para mostrar el paywall.
:::important
Para obtener la configuración de vista, debes activar el toggle **Show on device** en el Paywall Builder. De lo contrario, obtendrás una configuración de vista vacía y el paywall no se mostrará.
:::
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID")
.onSuccess { paywall ->
if (!paywall.hasViewConfiguration) {
return@onSuccess
}
val paywallView = AdaptyUI.createPaywallView(paywall = paywall)
paywallView?.present()
}
.onError { error ->
// handle the error
}
```
## 2. Mostrar el paywall \{#2-display-the-paywall\}
Ahora que tienes la configuración del paywall, basta con añadir unas pocas líneas para mostrarlo.
Para mostrar el paywall visual en la pantalla del dispositivo, primero debes configurarlo. Para ello, llama al método `AdaptyUI.createPaywallView()`:
```kotlin showLineNumbers
val paywallView = AdaptyUI.createPaywallView(paywall = paywall)
paywallView?.present()
```
Una vez que la vista se haya creado correctamente, puedes presentarla en la pantalla del dispositivo.
:::tip
Para más detalles sobre cómo mostrar un paywall, consulta nuestra [guía](kmp-present-paywalls).
:::
## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\}
Cuando los usuarios pulsan botones en el paywall, el SDK de Kotlin Multiplatform gestiona automáticamente las compras, la restauración, el cierre del paywall y la apertura de enlaces.
Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. O puede que quieras sobrescribir su comportamiento predeterminado.
Por ejemplo, aquí está el comportamiento predeterminado del botón de cierre. No necesitas añadirlo en el código, pero aquí puedes ver cómo se hace si fuera necesario.
:::tip
Lee nuestras guías sobre cómo gestionar [acciones](kmp-handle-paywall-actions) y [eventos](kmp-handling-events) de los botones.
:::
```kotlin showLineNumbers
AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
when (action) {
AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss()
}
}
})
```
## Próximos pasos \{#next-steps\}
Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox del App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall.
Ahora necesitas [comprobar el nivel de acceso de los usuarios](kmp-check-subscription-status) para asegurarte de mostrar un paywall o dar acceso a las funciones de pago a los usuarios correctos.
## Ejemplo completo \{#full-example\}
Aquí puedes ver cómo integrar todos estos pasos en tu app.
```kotlin showLineNumbers
// Set up the observer for handling paywall actions
AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
when (action) {
is AdaptyUIAction.CloseAction -> view.dismiss()
}
}
})
// Get and display the paywall
Adapty.getPaywall("YOUR_PLACEMENT_ID")
.onSuccess { paywall ->
if (!paywall.hasViewConfiguration) {
// Use custom logic
return@onSuccess
}
val paywallView = AdaptyUI.createPaywallView(paywall = paywall)
paywallView?.present()
}
.onError { error ->
// handle the error
}
```
---
# File: kmp-check-subscription-status
---
---
title: "Comprobar el estado de la suscripción en el SDK de Kotlin Multiplatform"
description: "Aprende a comprobar el estado de la suscripción en tu app de Kotlin Multiplatform con Adapty."
---
Para decidir si los usuarios pueden acceder a contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil.
Este artículo muestra cómo acceder al estado del perfil para decidir qué deben ver los usuarios: si mostrarles un paywall o darles acceso a las funciones de pago.
## Obtener el estado de la suscripción \{#get-subscription-status\}
Cuando decides si mostrar un paywall o contenido de pago a un usuario, compruebas su [nivel de acceso](access-level) en su perfil. Tienes dos opciones:
- Llama a `getProfile` si necesitas los datos más recientes del perfil de inmediato (como al iniciar la app) o quieres forzar una actualización.
- Configura **actualizaciones automáticas del perfil** para mantener una copia local que se refresca automáticamente cada vez que cambia el estado de la suscripción.
### Obtener el perfil \{#get-profile\}
La forma más sencilla de obtener el estado de la suscripción es usar el método `getProfile` para acceder al perfil:
```kotlin showLineNumbers
Adapty.getProfile()
.onSuccess { profile ->
// check the access
}
.onError { error ->
// handle the error
}
```
### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\}
Para recibir actualizaciones del perfil automáticamente en tu app:
1. Usa `Adapty.setOnProfileUpdatedListener()` para escuchar cambios en el perfil: Adapty llamará a este método automáticamente cada vez que cambie el estado de la suscripción del usuario.
2. Almacena los datos del perfil actualizado cuando se llame a este método, para poder usarlos en toda tu app sin hacer peticiones de red adicionales.
```kotlin showLineNumbers
class SubscriptionManager {
private var currentProfile: AdaptyProfile? = null
init {
// Listen for profile updates
Adapty.setOnProfileUpdatedListener { profile ->
currentProfile = profile
// Update UI, unlock content, etc.
}
}
// Use stored profile instead of calling getProfile()
fun hasAccess(): Boolean {
return currentProfile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true
}
}
```
:::note
Adapty llama automáticamente al listener de actualización del perfil cuando se inicia tu app, proporcionando datos de suscripción en caché incluso si el dispositivo está sin conexión.
:::
## Conectar el perfil con la lógica del paywall \{#connect-profile-with-paywall-logic\}
Cuando necesitas tomar decisiones inmediatas sobre si mostrar paywalls o dar acceso a funciones de pago, puedes comprobar el perfil del usuario directamente. Este enfoque es útil en escenarios como el inicio de la app, al entrar en secciones premium o antes de mostrar contenido específico.
```kotlin showLineNumbers
private fun checkAccessAndShowPaywall() {
// First, check if user has access
Adapty.getProfile()
.onSuccess { profile ->
val hasAccess = profile.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true
if (!hasAccess) {
// User doesn't have access, show paywall
showPaywall()
} else {
// User has access, show premium content
showPremiumContent()
}
}
.onError { error ->
// If we can't check access, show paywall as fallback
showPaywall()
}
}
private fun showPaywall() {
// Get and display paywall using the KMP SDK
Adapty.getPaywall("YOUR_PLACEMENT_ID")
.onSuccess { paywall ->
if (paywall.hasViewConfiguration) {
val paywallView = AdaptyUI.createPaywallView(paywall = paywall)
paywallView?.present()
} else {
// Handle remote config paywall or show custom UI
handleRemoteConfigPaywall(paywall)
}
}
.onError { error ->
// Handle paywall loading error
showError("Unable to load paywall")
}
}
private fun showPremiumContent() {
// Show your premium content here
// This is where you unlock paid features
}
```
## Próximos pasos \{#next-steps\}
Ahora que sabes cómo hacer seguimiento del estado de la suscripción, aprende a [trabajar con perfiles de usuario](kmp-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado.
---
# File: kmp-quickstart-identify
---
---
title: "Identificar usuarios en el SDK de Kotlin Multiplatform"
description: "Guía de inicio rápido para configurar Adapty para la gestión de suscripciones in-app en KMP."
---
:::important
Esta guía es para ti si tienes tu propio sistema de autenticación. Aquí aprenderás a trabajar con perfiles de usuario en Adapty para que se alineen con tu sistema de autenticación existente.
:::
La forma en que gestionas las compras de los usuarios depende del modelo de autenticación de tu app:
- Si tu app no usa autenticación de backend ni almacena datos de usuario, consulta la [sección sobre usuarios anónimos](#anonymous-users).
- Si tu app tiene (o tendrá) autenticación de backend, consulta la [sección sobre usuarios identificados](#identified-users).
**Conceptos clave**:
- Los **perfiles** son las entidades necesarias para que el SDK funcione. Adapty los crea automáticamente.
- Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**.
- Proporcionas el **customer user ID** para cruzar los perfiles de Adapty con tu sistema de autenticación interno.
Esto es lo que difiere entre usuarios anónimos e identificados:
| | Usuarios anónimos | Usuarios identificados |
|-------------------------|-------------------------------------------------------|-------------------------------------------------------------------------------------|
| **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras entre dispositivos mediante su customer user ID |
| **Gestión de perfiles** | Nuevos perfiles en cada reinstalación | El mismo perfil entre sesiones y dispositivos |
| **Persistencia de datos** | Los datos de usuarios anónimos están ligados a la instalación de la app | Los datos de usuarios identificados persisten entre instalaciones |
## Usuarios anónimos \{#anonymous-users\}
Si no tienes autenticación de backend, **no necesitas gestionar la autenticación en el código de la app**:
1. Cuando el SDK se activa en el primer inicio de la app, Adapty **crea un nuevo perfil para el usuario**.
2. Cuando el usuario compra algo en la app, esa compra se **asocia a su perfil de Adapty y a su cuenta en el store**.
3. Cuando el usuario **reinstala** la app o la instala desde un **nuevo dispositivo**, Adapty **crea un nuevo perfil anónimo al activarse**.
4. Si el usuario ya había realizado compras en tu app, por defecto sus compras se sincronizan automáticamente desde el App Store al activar el SDK.
Con usuarios anónimos, se crearán nuevos perfiles en cada instalación, pero eso no es un problema porque en los análisis de Adapty puedes [configurar qué se considerará una nueva instalación](general#4-installs-definition-for-analytics).
Para los usuarios anónimos, debes contar las instalaciones por **ID de dispositivo**. En este caso, cada instalación de la app en un dispositivo se cuenta como una instalación, incluidas las reinstalaciones.
:::note
Las restauraciones de copia de seguridad se comportan de forma diferente a las reinstalaciones. Por defecto, cuando un usuario restaura desde una copia de seguridad, el SDK conserva los datos en caché y no crea un nuevo perfil. Puedes configurar este comportamiento con el parámetro `withAppleClearDataOnBackup`. [Más información](sdk-installation-kotlin-multiplatform#clear-data-on-backup-restore).
:::
## Usuarios identificados \{#identified-users\}
Tienes dos opciones para identificar usuarios en la app:
- [**Durante el login/registro:**](#during-loginsignup) Si los usuarios inician sesión después de que tu app arranque, llama a `identify()` con un customer user ID cuando se autentiquen.
- [**Durante la activación del SDK:**](#during-the-sdk-activation) Si ya tienes un customer user ID almacenado cuando la app se inicia, envíalo al llamar a `activate()`.
:::important
Por defecto, cuando Adapty recibe una compra de un Customer User ID que ya está asociado a otro Customer User ID, el nivel de acceso se comparte, por lo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o deshabilitar el uso compartido completamente. Consulta el [artículo](general#6-sharing-paid-access-between-user-accounts) para más detalles.
:::
### Durante el login/registro \{#during-loginsignup\}
Si identificas a los usuarios después de que arranque la app (por ejemplo, tras iniciar sesión o registrarse), usa el método `identify` para establecer su customer user ID.
- Si **no has usado este customer user ID antes**, Adapty lo vinculará automáticamente al perfil actual.
- Si **ya has usado este customer user ID para identificar al usuario antes**, Adapty cambiará a trabajar con el perfil asociado a ese customer user ID.
:::important
Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo.
:::
Espera a que `identify` se complete (en su callback `onSuccess`) antes de llamar a otros métodos del SDK. Las llamadas concurrentes pueden acabar en el perfil anónimo. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order).
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") // Único para cada usuario
.onSuccess {
// identify exitoso
}
.onError { error ->
// gestionar el error
}
```
### Durante la activación del SDK \{#during-the-sdk-activation\}
Si ya conoces el customer user ID cuando activas el SDK, puedes enviarlo en el método `activate` en lugar de llamar a `identify` por separado.
Si conoces un customer user ID pero solo lo estableces después de la activación, eso significa que, al activarse, Adapty creará un nuevo perfil anónimo y cambiará al existente solo cuando llames a `identify`.
Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el nuevo perfil creado en la activación se vinculará automáticamente al customer user ID.
:::note
Por defecto, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan en función de los ID de dispositivo.
Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera tras reinstalar la app.
No depende de si es una primera instalación o una repetida, ni de si se usa un customer user ID existente.
Crear un perfil (al activar el SDK o al cerrar sesión), iniciar sesión o actualizar la app sin reinstalarla no genera eventos de instalación adicionales.
Si quieres contar las instalaciones en función de usuarios únicos en lugar de dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics).
:::
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Los customer user IDs deben ser únicos para cada usuario. Si hardcodeas el valor del parámetro, todos los usuarios se considerarán como uno solo.
.build()
```
### Cerrar sesión de usuarios \{#log-users-out\}
Si tienes un botón para cerrar la sesión de los usuarios, usa el método `logout`.
:::important
Cerrar la sesión de los usuarios crea un nuevo perfil anónimo para el usuario.
:::
```kotlin showLineNumbers
Adapty.logout()
.onSuccess {
// cierre de sesión exitoso
}
.onError { error ->
// gestionar el error
}
```
:::info
Para volver a iniciar sesión en la app, usa el método `identify`.
:::
### Permitir compras sin inicio de sesión \{#allow-purchases-without-login\}
Si tus usuarios pueden realizar compras tanto antes como después de iniciar sesión en tu app, debes asegurarte de que mantendrán el acceso una vez que inicien sesión:
1. Cuando un usuario sin sesión iniciada realiza una compra, Adapty la vincula a su ID de perfil anónimo.
2. Cuando el usuario inicia sesión en su cuenta, Adapty pasa a trabajar con su perfil identificado.
- Si es un nuevo customer user ID (por ejemplo, la compra se realizó antes del registro), Adapty asigna el customer user ID al perfil actual, por lo que todo el historial de compras se mantiene.
- Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso real tras el cambio de perfil. Puedes llamar a [`getProfile`](kmp-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](kmp-check-subscription-status) para que los datos se sincronicen automáticamente.
## Próximos pasos \{#next-steps\}
¡Enhorabuena! Has implementado la lógica de pago in-app en tu app. ¡Te deseamos lo mejor con la monetización de tu app!
Para sacar aún más partido a Adapty, puedes explorar estos temas:
- [**Pruebas**](troubleshooting-test-purchases): Asegúrate de que todo funciona correctamente
- [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis en una sola línea de código
- [**Establecer atributos de perfil personalizados**](kmp-setting-user-attributes): Añade atributos personalizados a los perfiles de usuario y crea segmentos para lanzar pruebas A/B o mostrar diferentes paywalls a distintos usuarios
---
# File: adapty-cursor-kmp
---
---
title: "Integra Adapty en tu app de Kotlin Multiplatform con ayuda de IA"
description: "Una guía paso a paso para integrar Adapty en tu app de Kotlin Multiplatform usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA."
---
Esta página cubre dos formas de integrar Adapty en tu app de Kotlin Multiplatform. Usa la skill de integración del SDK que se describe a continuación para un flujo automatizado de principio a fin, o sigue el recorrido manual más adelante.
## Usa la skill de integración del SDK (beta) \{#use-the-sdk-integration-skill-beta\}
La [skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de extremo a extremo: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. El recorrido manual más adelante es la alternativa si tu herramienta no admite el formato Claude Skills.
**Herramientas compatibles**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI.
### Instalación \{#install\}
Elige el formato para tu herramienta. La lista completa está en el [README de la skill](https://github.com/adaptyteam/adapty-sdk-integration-skill).
- **Claude Code**: Ejecuta `claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill` y luego `claude plugin install adapty-sdk-integration@adapty` desde tu terminal.
- **GitHub Copilot CLI**: Ejecuta `gh skill install adaptyteam/adapty-sdk-integration-skill`.
- **Gemini CLI**: Ejecuta `gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill`.
- **OpenAI Codex u otra herramienta**: Clona el repositorio y copia `plugins/adapty-sdk-integration/skills/adapty-sdk-integration/` en el directorio de skills de tu herramienta.
### Ejecución \{#run\}
En tu proyecto, ejecuta `/adapty-sdk-integration`. La skill detecta tu plataforma y hace algunas preguntas de configuración. Luego recorre la configuración del dashboard, la instalación del SDK, el paywall y la verificación, consultando la documentación de Adapty relevante en cada etapa.
:::note
La skill está en beta. Si se detiene o se comporta de forma inesperada, el recorrido manual que aparece más abajo cubre cada etapa paso a paso.
:::
## Antes de empezar: configuración del dashboard \{#before-you-start-dashboard-setup\}
Adapty requiere cierta configuración en el dashboard antes de escribir código con el SDK. Puedes hacerlo con una skill de LLM interactiva o manualmente desde el Dashboard.
### Con la skill (recomendado) \{#skill-approach-recommended\}
La skill de la CLI de Adapty permite que tu LLM configure tu app, productos, niveles de acceso, paywalls y placements directamente, sin necesidad de abrir el Dashboard en cada paso. Solo necesitas [conectar tus stores](integrate-payments) en el Dashboard.
```
npx skills add adaptyteam/adapty-cli --skill adapty-cli
```
Una vez añadida la skill, ejecuta `/adapty-cli` en tu agente. Te guiará por cada paso, incluyendo cuándo abrir el Dashboard para conectar tus stores.
### Con el Dashboard \{#dashboard-approach\}
Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir código. Tu LLM no puede consultar los valores del dashboard por ti — tendrás que proporcionárselos.
1. **Conecta tus stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta tanto App Store como Google Play si tu app KMP tiene como objetivo ambas plataformas. Esto es necesario para que las compras funcionen.
[Conectar stores](integrate-payments)
2. **Copia tu clave pública del SDK**: En el Adapty Dashboard, ve a **App settings → General** y busca la sección **API keys**. En el código, esta es la cadena que pasas al constructor de configuración de Adapty.
3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No haces referencia a los productos directamente en el código — Adapty los entrega a través de los paywalls.
[Añadir productos](quickstart-products)
4. **Crea un paywall y un placement**: En el Adapty Dashboard, crea un paywall en la página **Paywalls** y asígnalo a un placement en la página **Placements**. En el código, el ID del placement es la cadena que pasas a `Adapty.getPaywall("YOUR_PLACEMENT_ID")`.
[Crear paywall](quickstart-paywalls)
5. **Configura los niveles de acceso**: En el Adapty Dashboard, configúralos por producto en la página **Products**. En el código, la cadena se comprueba en `profile.accessLevels["premium"]?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago tienen acceso a funciones diferentes según el producto (por ejemplo, un plan `basic` frente a un plan `pro`), [crea niveles de acceso adicionales](assigning-access-level-to-a-product) antes de empezar a programar.
:::tip
Una vez que tengas los cinco, estás listo para escribir código. Dile a tu LLM: "Mi clave pública del SDK es X, mi ID de placement es Y" para que pueda generar código correcto de inicialización y obtención del paywall.
:::
### Configura cuando estés listo \{#set-up-when-ready\}
Esto no es necesario para empezar a programar, pero lo necesitarás a medida que tu integración madure:
- **Pruebas A/B**: Configúralas en la página **Placements**. No se necesita ningún cambio de código.
[Pruebas A/B](ab-tests)
- **Paywalls y placements adicionales**: Añade más llamadas a `getPaywall` con diferentes IDs de placement.
- **Integraciones de analíticas**: Configúralas en la página **Integrations**. La configuración varía según la integración. Consulta [integraciones de analíticas](analytics-integration) e [integraciones de atribución](attribution-integration).
## Dale documentación de Adapty a tu LLM \{#feed-adapty-docs-to-your-llm\}
### Usa Context7 (recomendado) \{#use-context7-recommended\}
[Context7](https://context7.com) es un servidor MCP que da a tu LLM acceso directo a la documentación actualizada de Adapty. Tu LLM obtiene los documentos correctos automáticamente según lo que preguntes — sin necesidad de pegar URLs manualmente.
Context7 funciona con **Cursor**, **Claude Code**, **Windsurf** y otras herramientas compatibles con MCP. Para configurarlo, ejecuta:
```
npx ctx7 setup
```
Esto detecta tu editor y configura el servidor Context7. Para la configuración manual, consulta el [repositorio de Context7 en GitHub](https://github.com/upstash/context7).
Una vez configurado, haz referencia a la librería de Adapty en tus prompts:
```
Use the adaptyteam/adapty-docs library to look up how to install the Kotlin Multiplatform SDK
```
:::warning
Aunque Context7 elimina la necesidad de pegar enlaces a la documentación manualmente, el orden de implementación es importante. Sigue el [recorrido de implementación](#implementation-walkthrough) que aparece a continuación paso a paso para asegurarte de que todo funciona.
:::
### Usa documentación en texto plano \{#use-plain-text-docs\}
Puedes acceder a cualquier artículo de Adapty en texto plano Markdown. Añade `.md` al final de su URL o haz clic en **Copy for LLM** bajo el título del artículo. Por ejemplo: [adapty-cursor-kmp.md](https://adapty.io/docs/es/adapty-cursor-kmp.md).
Cada etapa del [recorrido de implementación](#implementation-walkthrough) que aparece a continuación incluye un bloque "Envía esto a tu LLM" con enlaces `.md` para pegar.
Para obtener más documentación a la vez, consulta los [archivos de índice y subconjuntos por plataforma](#plain-text-doc-index-files) que aparecen más abajo.
## Recorrido de implementación \{#implementation-walkthrough\}
El resto de esta guía recorre la integración de Adapty en orden de implementación. Cada etapa incluye los documentos que debes enviar a tu LLM, lo que deberías ver cuando termines y los problemas más comunes.
### Planifica tu integración \{#plan-your-integration\}
Antes de lanzarte al código, pide a tu LLM que analice tu proyecto y cree un plan de implementación. Si tu herramienta de IA admite un modo de planificación (como el modo plan de Cursor o Claude Code), úsalo para que el LLM pueda leer tanto la estructura de tu proyecto como la documentación de Adapty antes de escribir código.
Dile a tu LLM qué enfoque usas para las compras — esto afecta a las guías que debe seguir:
- [**Adapty Paywall Builder**](adapty-paywall-builder): Creas los paywalls en el constructor no-code de Adapty y el SDK los renderiza automáticamente.
- [**Paywalls creados manualmente**](kmp-making-purchases): Construyes tu propia interfaz de paywall en código pero sigues usando Adapty para obtener productos y gestionar compras.
- [**Modo Observer**](observer-vs-full-mode): Mantienes tu infraestructura de compras existente y usas Adapty solo para analíticas e integraciones.
¿No sabes cuál elegir? Lee la [tabla comparativa del inicio rápido](kmp-quickstart-paywalls).
### Instala y configura el SDK \{#install-and-configure-the-sdk\}
Añade la dependencia del SDK de Adapty mediante Gradle y actívalo con tu clave pública del SDK. Esta es la base — todo lo demás depende de esto.
**Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-kotlin-multiplatform)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/sdk-installation-kotlin-multiplatform.md
```
:::tip[Punto de control]
- **Esperado:** La app compila y se ejecuta. El Logcat (Android) o la consola de Xcode (iOS) muestra el log de activación de Adapty.
- **Problema frecuente:** "Public API key is missing" → comprueba que hayas sustituido el marcador de posición por tu clave real de App settings.
:::
### Muestra paywalls y gestiona compras \{#show-paywalls-and-handle-purchases\}
Obtén un paywall por ID de placement, muéstralo y gestiona los eventos de compra. Las guías que necesitas dependen de cómo gestionas las compras.
Prueba cada compra en el sandbox a medida que avanzas — no esperes hasta el final. Consulta [Probar compras en sandbox](test-purchases-in-sandbox) para las instrucciones de configuración.
**Guías:**
- [Habilitar compras con paywalls (inicio rápido)](kmp-quickstart-paywalls)
- [Obtener paywalls del Paywall Builder y su configuración](kmp-get-pb-paywalls)
- [Mostrar paywalls](kmp-present-paywalls)
- [Gestionar eventos del paywall](kmp-handling-events)
- [Responder a acciones de botones](kmp-handle-paywall-actions)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/kmp-quickstart-paywalls.md
- https://adapty.io/docs/es/kmp-get-pb-paywalls.md
- https://adapty.io/docs/es/kmp-present-paywalls.md
- https://adapty.io/docs/es/kmp-handling-events.md
- https://adapty.io/docs/es/kmp-handle-paywall-actions.md
```
:::tip[Punto de control]
- **Esperado:** El paywall aparece con los productos que has configurado. Al pulsar un producto se activa el diálogo de compra en sandbox.
- **Problema frecuente:** Paywall vacío o error en `getPaywall` → verifica que el ID del placement coincida exactamente con el del dashboard y que el placement tenga una audiencia asignada.
:::
**Guías:**
- [Habilitar compras en tu paywall personalizado (inicio rápido)](kmp-quickstart-manual)
- [Obtener paywalls y productos](fetch-paywalls-and-products-kmp)
- [Renderizar paywall diseñado con Remote Config](present-remote-config-paywalls-kmp)
- [Realizar compras](kmp-making-purchases)
- [Restaurar compras](kmp-restore-purchase)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/kmp-quickstart-manual.md
- https://adapty.io/docs/es/fetch-paywalls-and-products-kmp.md
- https://adapty.io/docs/es/present-remote-config-paywalls-kmp.md
- https://adapty.io/docs/es/kmp-making-purchases.md
- https://adapty.io/docs/es/kmp-restore-purchase.md
```
:::tip[Punto de control]
- **Esperado:** Tu paywall personalizado muestra los productos obtenidos de Adapty. Al pulsar un producto se activa el diálogo de compra en sandbox.
- **Problema frecuente:** Array de productos vacío → verifica que el paywall tenga productos asignados en el dashboard y que el placement tenga una audiencia.
:::
**Guías:**
- [Descripción general del modo Observer](observer-vs-full-mode)
- [Implementar el modo Observer](implement-observer-mode-kmp)
- [Reportar transacciones en modo Observer](report-transactions-observer-mode-kmp)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/observer-vs-full-mode.md
- https://adapty.io/docs/es/implement-observer-mode-kmp.md
- https://adapty.io/docs/es/report-transactions-observer-mode-kmp.md
```
:::tip[Punto de control]
- **Esperado:** Después de una compra en sandbox usando tu flujo de compras existente, la transacción aparece en el **Event Feed** del dashboard de Adapty.
- **Problema frecuente:** Sin eventos → verifica que estás reportando las transacciones a Adapty y que las notificaciones del servidor están configuradas para ambos stores.
:::
### Comprueba el estado de la suscripción \{#check-subscription-status\}
Tras una compra, comprueba el perfil del usuario para ver si hay un nivel de acceso activo y restringir el contenido premium.
**Guía:** [Comprobar el estado de la suscripción](kmp-check-subscription-status)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/kmp-check-subscription-status.md
```
:::tip[Punto de control]
- **Esperado:** Tras una compra en sandbox, `profile.accessLevels["premium"]?.isActive` devuelve `true`.
- **Problema frecuente:** `accessLevels` vacío tras la compra → comprueba que el producto tiene un nivel de acceso asignado en el dashboard.
:::
### Identifica a los usuarios \{#identify-users\}
Vincula las cuentas de usuario de tu app con los perfiles de Adapty para que las compras persistan entre dispositivos.
:::important
Omite este paso si tu app no tiene autenticación.
:::
**Guía:** [Identificar usuarios](kmp-quickstart-identify)
Envía esto a tu LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/es/kmp-quickstart-identify.md
```
:::tip[Punto de control]
- **Esperado:** Tras llamar a `Adapty.identify("your-user-id")`, la sección **Profiles** del dashboard muestra tu ID de usuario personalizado.
- **Problema frecuente:** Llama a `identify` después de la activación pero antes de obtener los paywalls para evitar la atribución a perfiles anónimos.
:::
### Prepárate para el lanzamiento \{#prepare-for-release\}
Una vez que tu integración funcione en el sandbox, repasa la lista de verificación de lanzamiento para asegurarte de que todo está listo para producción.
**Guía:** [Lista de verificación de lanzamiento](release-checklist)
Envía esto a tu LLM:
```
Read these Adapty docs before releasing:
- https://adapty.io/docs/es/release-checklist.md
```
:::tip[Punto de control]
- **Esperado:** Todos los elementos de la lista confirmados: conexiones de stores, notificaciones del servidor, flujo de compras, comprobaciones del nivel de acceso y requisitos de privacidad.
- **Problema frecuente:** Notificaciones del servidor faltantes → configura las notificaciones del servidor de App Store en **App settings → iOS SDK** y las notificaciones en tiempo real para desarrolladores de Google Play en **App settings → Android SDK**.
:::
## Archivos de índice de documentación en texto plano \{#plain-text-doc-index-files\}
Si necesitas dar a tu LLM un contexto más amplio más allá de páginas individuales, ofrecemos archivos de índice que listan o combinan toda la documentación de Adapty:
- [`llms.txt`](https://adapty.io/docs/es/llms.txt): Lista todas las páginas con enlaces `.md`. Un [estándar emergente](https://llmstxt.org/) para hacer los sitios web accesibles a los LLMs. Ten en cuenta que para algunos agentes de IA (por ejemplo, ChatGPT) necesitarás descargar `llms.txt` y subirlo al chat como archivo.
- [`llms-full.txt`](https://adapty.io/docs/es/llms-full.txt): Toda la documentación del sitio de Adapty combinada en un único archivo. Muy grande — úsalo solo cuando necesites el panorama completo.
- [`kmp-llms.txt`](https://adapty.io/docs/es/kmp-llms.txt) y [`kmp-llms-full.txt`](https://adapty.io/docs/es/kmp-llms-full.txt) específicos de Kotlin Multiplatform: Subconjuntos por plataforma que ahorran tokens en comparación con el sitio completo.
---
# File: kmp-paywalls
---
---
title: "Paywalls in Kotlin Multiplatform SDK"
description: "Learn how to work with paywalls in your Kotlin Multiplatform app with Adapty SDK."
---
## Display paywalls
### Adapty Paywall Builder
:::tip
To get started with the Adapty Paywall Builder paywalls quickly, see our [quickstart guide](kmp-quickstart-paywalls).
:::
### Implement paywalls manually
For more guides on implementing paywalls and handling purchases manually, see the [category](kmp-implement-paywalls-manually).
## Useful features
---
# File: kmp-get-pb-paywalls
---
---
title: "Obtener paywalls del Paywall Builder y su configuración en el SDK de Kotlin Multiplatform"
description: "Aprende a recuperar paywalls de PB en Adapty para un mejor control de suscripciones en tu app Kotlin Multiplatform."
---
Después de [diseñar la parte visual de tu paywall](adapty-paywall-builder) con el nuevo Paywall Builder en el Adapty Dashboard, puedes mostrarlo en tu app móvil. El primer paso es obtener el paywall asociado al placement y su configuración de vista, tal como se describe a continuación.
Ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si estás implementando tus paywalls de forma manual, consulta el tema [Obtener paywalls y productos para paywalls con Remote Config en tu app móvil](fetch-paywalls-and-products-kmp).
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
Antes de comenzar a mostrar paywalls en tu app móvil (haz clic para expandir)
1. [Crea tus productos](create-product) en el Adapty Dashboard.
2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard.
3. [Crea placements e incorpora tu paywall en ellos](create-placement) en el Adapty Dashboard.
4. Instala el [SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu app móvil.
## Obtener un paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\}
Si [diseñaste un paywall con el Paywall Builder](adapty-paywall-builder), no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese tipo de paywall contiene tanto lo que debe mostrarse como la forma en que debe presentarse. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y luego presentarlo en tu app.
Para garantizar un rendimiento óptimo, es fundamental recuperar el paywall y su [configuración de vista](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) lo antes posible, dejando tiempo suficiente para que las imágenes se descarguen antes de mostrárselas al usuario.
Para obtener un paywall, usa el método `getPaywall`:
```kotlin showLineNumbers
Adapty.getPaywall(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
loadTimeout = 5.seconds
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
Parámetros:
| Parámetro | Presencia | Descripción |
|---------|--------|-----------|
| **placementId** | obligatorio | El identificador del [placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la [localización del paywall](add-paywall-locale-in-adapty-paywall-builder). Se espera que este parámetro sea un código de idioma compuesto por una o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda, a la región.
Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.
Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.
|
| **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | Por defecto, el SDK intentará cargar datos desde el servidor y devolverá datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban la información más actualizada.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver datos en caché si existen. En ese caso, los usuarios podrían no recibir los datos más recientes, pero tendrán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.
Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra al desinstalarla o mediante limpieza manual.
El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada regularmente descrita arriba y los [paywalls de respaldo](fallback-paywalls). También usamos CDN para obtener paywalls más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para que siempre obtengas la versión más reciente de tus paywalls, garantizando fiabilidad incluso cuando la conexión a internet es escasa.
|
| **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el límite, se devolverán datos en caché o el fallback local.
Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede estar compuesta de distintas solicitudes internas.
Para Kotlin Multiplatform: puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` proviene de `import com.adapty.utils.seconds`), o `TimeInterval.seconds(5)`. Para no establecer límite, usa `TimeInterval.INFINITE`.
|
Parámetros de respuesta:
| Parámetro | Descripción |
| :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Paywall | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) con una lista de IDs de productos, el identificador del paywall, Remote Config y otras propiedades. |
## Obtener la configuración de vista de un paywall diseñado con Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\}
:::important
Asegúrate de activar el toggle **Show on device** en el Paywall Builder. Si esta opción no está activada, la configuración de vista no estará disponible para recuperarse.
:::
Tras obtener el paywall, comprueba si incluye un `ViewConfiguration`, lo que indica que fue creado con Paywall Builder. Esto te orientará sobre cómo mostrar el paywall. Si el `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no lo está, [manéjalo como un paywall de Remote Config](present-remote-config-paywalls-kmp).
Usa el método `createPaywallView` para cargar la configuración de vista.
```kotlin showLineNumbers
if (paywall.hasViewConfiguration) {
AdaptyUI.createPaywallView(
paywall = paywall,
loadTimeout = 5.seconds,
preloadProducts = true
).onSuccess { paywallView ->
// use paywallView
}.onError { error ->
// handle the error
}
} else {
// use your custom logic
}
```
| Parámetro | Presencia | Descripción |
| :--------------------------- | :------------- | :----------------------------------------------------------- |
| **paywall** | obligatorio | Un objeto `AdaptyPaywall` para obtener un controlador del paywall deseado. |
| **loadTimeout** | opcional | Este valor limita el tiempo de espera para este método. Si se alcanza el límite, se devolverán datos en caché o el fallback local. Ten en cuenta que en casos excepcionales este método puede superar ligeramente el tiempo indicado en `loadTimeout`, ya que la operación puede estar compuesta de distintas solicitudes internas. Puedes usar funciones de extensión como `5.seconds` de `kotlin.time.Duration.Companion`. |
| **preloadProducts** | opcional | Establécelo en `true` para precargar productos y mejorar el rendimiento. Cuando está activado, los productos se cargan con antelación, reduciendo el tiempo necesario para mostrar el paywall. |
| **productPurchaseParams** | opcional | Un mapa de [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) a [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Úsalo para configurar parámetros específicos de compra, como ofertas personalizadas o parámetros de actualización de suscripción para productos individuales del paywall. |
:::note
Si usas varios idiomas, aprende a añadir una [localización al Paywall Builder](add-paywall-locale-in-adapty-paywall-builder).
:::
Una vez cargado, [presenta el paywall](kmp-present-paywalls).
## Obtener un paywall para la audiencia por defecto y cargarlo más rápido \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\}
Normalmente, los paywalls se obtienen casi de inmediato, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, si tienes muchas audiencias y paywalls y tus usuarios tienen una conexión a internet débil, puede que obtener un paywall tarde más de lo deseable. En esas situaciones, puede que prefieras mostrar un paywall por defecto para garantizar una buena experiencia de usuario en lugar de no mostrar ninguno.
Para resolver esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement indicado para la audiencia **All Users**. No obstante, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, tal como se detalla en la sección [Obtener un paywall diseñado con Paywall Builder](#fetch-paywall-designed-with-paywall-builder) anterior.
:::warning
Por qué recomendamos usar `getPaywall`
El método `getPaywallForDefaultAudience` tiene algunas desventajas importantes:
- **Posibles problemas de compatibilidad con versiones anteriores**: si necesitas mostrar paywalls distintos para diferentes versiones de la app (actual y futuras), pueden surgir complicaciones. Tendrás que diseñar paywalls que sean compatibles con la versión actual (antigua) o asumir que los usuarios con esa versión puedan encontrar problemas con paywalls que no se renderizan correctamente.
- **Pérdida de segmentación**: todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes la segmentación personalizada (incluyendo por países, atribución de marketing o tus propios atributos personalizados).
Si estás dispuesto a aceptar estas desventajas para beneficiarte de una carga más rápida del paywall, usa el método `getPaywallForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getPaywall` descrito [arriba](#fetch-paywall-designed-with-paywall-builder).
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
| Parámetro | Presencia | Descripción |
|---------|--------|-----------|
| **placementId** | obligatorio | El identificador del [placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda, a la región.
Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.
Consulta [Localizaciones y códigos de idioma](localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.
|
| **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | Por defecto, el SDK intentará cargar datos desde el servidor y devolverá datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban la información más actualizada.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver datos en caché si existen. En ese caso, los usuarios podrían no recibir los datos más recientes, pero tendrán tiempos de carga más rápidos, independientemente de la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.
Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra al desinstalarla o mediante limpieza manual.
|
## Personalizar assets \{#customize-assets\}
Para personalizar imágenes y vídeos en tu paywall, implementa los assets personalizados.
Las imágenes y vídeos destacados tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de assets personalizados, puedes apuntar a estos elementos por sus IDs y personalizar su comportamiento.
Para otras imágenes y vídeos, necesitas [establecer un ID personalizado](custom-media) en el dashboard de Adapty.
Por ejemplo, puedes:
- Mostrar una imagen o vídeo diferente a algunos usuarios.
- Mostrar una imagen de vista previa local mientras se carga la imagen principal remota.
- Mostrar una imagen de vista previa antes de reproducir un vídeo.
:::important
Para usar esta funcionalidad, actualiza el SDK de Adapty a la versión 3.7.0 o superior.
:::
A continuación se muestra un ejemplo de cómo proporcionar assets personalizados a través de un mapa:
:::info
El SDK de Kotlin Multiplatform solo admite assets locales. Para contenido remoto, debes descargar y almacenar en caché los assets localmente antes de usarlos como assets personalizados.
:::
```kotlin showLineNumbers
// Import generated Res class for accessing resources
viewModelScope.launch {
// Get URIs for bundled resources using Res.getUri()
val heroImagePath = Res.getUri("files/images/hero_image.png")
val demoVideoPath = Res.getUri("files/videos/demo_video.mp4")
// Or read image as byte data
val imageByteData = Res.readBytes("files/images/avatar.png")
// Create custom assets map
val customAssets: Map = mapOf(
// Load image from app resources (bundled with the app)
// Files should be placed in commonMain/composeResources/files/
"hero_image" to AdaptyCustomAsset.localImageResource(
path = heroImagePath
),
// Or use image byte data
"avatar" to AdaptyCustomAsset.localImageData(
data = imageByteData
),
// Load video from app resources
"demo_video" to AdaptyCustomAsset.localVideoResource(
path = demoVideoPath
),
// Or use a video file from device storage
"intro_video" to AdaptyCustomAsset.localVideoFile(
path = "/path/to/local/video.mp4"
),
// Apply custom brand colors
"brand_primary" to AdaptyCustomAsset.color(
colorHex = "#FF6B35"
),
// Create gradient background
"card_gradient" to AdaptyCustomAsset.linearGradient(
colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"),
stops = listOf(0.0f, 0.5f, 1.0f)
)
)
// Use custom assets when creating paywall view
AdaptyUI.createPaywallView(
paywall = paywall,
customAssets = customAssets
).onSuccess { paywallView ->
// Present the paywall with custom assets
paywallView.present()
}.onError { error ->
// Handle the error - paywall will fall back to default appearance
}
}
```
:::note
Si un asset no se encuentra o falla al cargarse, el paywall volverá a su apariencia predeterminada configurada en el Paywall Builder.
:::
---
# File: kmp-present-paywalls
---
---
title: "Kotlin Multiplatform - Presentar paywalls del nuevo Paywall Builder"
description: "Aprende a presentar paywalls en Kotlin Multiplatform para una monetización efectiva."
---
Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app para mostrárselo al usuario. Ese paywall ya contiene tanto lo que se debe mostrar como la forma en que debe mostrarse.
:::warning
Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder**. El proceso de presentación de paywalls es diferente para los paywalls diseñados con Remote Config y para el [modo Observer](observer-vs-full-mode).
Para presentar **paywalls con Remote Config**, consulta [Renderizar paywalls diseñados con Remote Config](present-remote-config-paywalls-kmp).
:::
El SDK de Adapty para Kotlin Multiplatform ofrece dos formas de presentar paywalls:
- **Con Compose Multiplatform**
- **Sin Compose Multiplatform**
## Con Compose Multiplatform \{#with-compose-multiplatform\}
Para mostrar un paywall, usa el método `view.present()` sobre el `view` creado por el método [`createPaywallView`](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Cada `view` solo puede usarse una vez. Si necesitas mostrar el paywall de nuevo, llama a `createPaywallView` otra vez para crear una nueva instancia de `view`.
:::warning
Reutilizar el mismo `view` sin recrearlo puede provocar un error.
:::
```kotlin showLineNumbers title="Kotlin Multiplatform"
viewModelScope.launch {
AdaptyUI.createPaywallView(paywall = paywall).onSuccess { view ->
view.present()
}.onError { error ->
// handle the error
}
}
```
### Mostrar diálogo \{#show-dialog\}
Usa este método en lugar de los diálogos de alerta nativos cuando se presenta una vista de paywall en Android. En Android, las alertas normales aparecen detrás de la vista del paywall, lo que las hace invisibles para los usuarios. Este método garantiza que el diálogo se muestre correctamente por encima del paywall en todas las plataformas.
```kotlin showLineNumbers title="Kotlin Multiplatform"
viewModelScope.launch {
view.showDialog(
title = "Close paywall?",
content = "You will lose access to exclusive offers.",
primaryActionTitle = "Stay",
secondaryActionTitle = "Close"
).onSuccess { action ->
if (action == AdaptyUIDialogActionType.SECONDARY) {
// User confirmed - close the paywall
view.dismiss()
}
// If primary - do nothing, user stays
}.onError { error ->
// handle the error
}
}
```
### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\}
Configura cómo se presenta el paywall en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FULLSCREEN` (por defecto) o `AdaptyUIIOSPresentationStyle.PAGESHEET`.
```kotlin showLineNumbers
viewModelScope.launch {
val view = AdaptyUI.createPaywallView(paywall = paywall).getOrNull()
view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET)
}
```
## Sin Compose Multiplatform \{#without-compose-multiplatform\}
:::note
`createNativePaywallView` forma parte del módulo principal `io.adapty:adapty-kmp`. Si tu proyecto no usa Compose Multiplatform, no necesitas la dependencia `io.adapty:adapty-kmp-ui`.
:::
Para embeber un paywall sin Compose Multiplatform, llama a `createNativePaywallView`. Devuelve un `AdaptyNativePaywallView` que puedes añadir a tu layout:
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"
val nativeView = AdaptyUI.createNativePaywallView(
context = context,
viewModelStoreOwner = activity,
paywall = paywall,
observer = myPaywallObserver,
)
// Embed in your Compose layout:
AndroidView(
factory = { nativeView.view },
modifier = Modifier.fillMaxSize()
)
```
Dado que los métodos por defecto de la interfaz KMP se vuelven `@required` en Swift, no puedes implementar `AdaptyUIPaywallsEventsObserver` directamente desde Swift. Primero declara una clase base abierta en `iosMain`:
```kotlin showLineNumbers title="iosMain (Kotlin)"
open class BasePaywallObserver : AdaptyUIPaywallsEventsObserver
```
Luego crea una subclase en Swift, sobreescribiendo solo lo que necesitas:
```swift showLineNumbers title="Swift"
class MyPaywallObserver: BasePaywallObserver {
override func paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: any AdaptyUIAction) {
if action is AdaptyUIActionCloseAction {
// remove nativeView from your view hierarchy
}
}
}
let nativeView = AdaptyUI.shared.createNativePaywallView(
paywall: paywall,
observer: MyPaywallObserver()
)
// nativeView.viewController is a UIViewController.
// Add it to your SwiftUI view or UIKit hierarchy.
```
### Eliminar la vista \{#dispose-the-view\}
Llama a `dispose()` cuando vayas a retirar la vista de tu layout. Esto cancela el registro del listener de eventos y libera los recursos internos.
```kotlin showLineNumbers title="Kotlin Multiplatform"
nativeView.dispose()
```
## Etiquetas personalizadas \{#custom-tags\}
Las etiquetas personalizadas te permiten evitar crear paywalls separados para distintos escenarios. Imagina un único paywall que se adapta dinámicamente según los datos del usuario. Por ejemplo, en lugar de un genérico "¡Hola!", podrías saludar al usuario por su nombre: "¡Hola, Juan!" o "¡Hola, Ana!".
Algunos casos de uso de las etiquetas personalizadas:
- Mostrar el nombre o email del usuario en el paywall.
- Mostrar el día de la semana para impulsar las ventas (p. ej., "Feliz jueves").
- Añadir detalles personalizados sobre los productos que vendes (como el nombre de un programa de fitness o un número de teléfono en una app VoIP).
Las etiquetas personalizadas te ayudan a crear un paywall flexible que se adapta a distintas situaciones, haciendo la interfaz de tu app más personalizada y atractiva.
:::warning
En algunos casos, tu app puede no saber con qué reemplazar una etiqueta personalizada, especialmente si los usuarios están en una versión antigua del SDK de AdaptyUI. Para evitarlo, añade siempre un texto de respaldo que sustituya las líneas que contengan etiquetas personalizadas desconocidas. Sin esto, los usuarios podrían ver las etiquetas como código (``).
:::
Para usar etiquetas personalizadas en tu paywall, pásalas al crear la vista del paywall:
```kotlin showLineNumbers title="Kotlin Multiplatform"
viewModelScope.launch {
val customTags = mapOf(
"USERNAME" to "John",
"DAY_OF_WEEK" to "Thursday"
)
AdaptyUI.createPaywallView(
paywall = paywall,
customTags = customTags
).onSuccess { view ->
view.present()
}.onError { error ->
// handle the error
}
}
```
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"
val customTags = mapOf(
"USERNAME" to "John",
"DAY_OF_WEEK" to "Thursday"
)
val nativeView = AdaptyUI.createNativePaywallView(
context = context,
viewModelStoreOwner = activity,
paywall = paywall,
observer = myPaywallObserver,
customTags = customTags,
)
```
```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)"
val customTags = mapOf(
"USERNAME" to "John",
"DAY_OF_WEEK" to "Thursday"
)
val nativeView = AdaptyUI.createNativePaywallView(
paywall = paywall,
observer = myPaywallObserver,
customTags = customTags,
)
```
## Temporizadores personalizados \{#custom-timers\}
El temporizador del paywall es una herramienta excelente para promocionar ofertas especiales y de temporada con un límite de tiempo. Sin embargo, es importante tener en cuenta que este temporizador no está vinculado a la validez de la oferta ni a la duración de la campaña. Es simplemente una cuenta atrás independiente que comienza desde el valor que establezcas y disminuye hasta cero. Cuando el temporizador llega a cero, no ocurre nada: simplemente se queda en cero.
Puedes personalizar el texto antes y después del temporizador para crear el mensaje deseado, por ejemplo: "La oferta termina en: 10:00 seg."
Para usar temporizadores personalizados en tu paywall, pásalos al crear la vista del paywall:
```kotlin showLineNumbers title="Kotlin Multiplatform"
viewModelScope.launch {
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
"CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
)
AdaptyUI.createPaywallView(
paywall = paywall,
customTimers = customTimers
).onSuccess { view ->
view.present()
}.onError { error ->
// handle the error
}
}
```
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
"CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
)
val nativeView = AdaptyUI.createNativePaywallView(
context = context,
viewModelStoreOwner = activity,
paywall = paywall,
observer = myPaywallObserver,
customTimers = customTimers,
)
```
```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)"
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0),
"CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59)
)
val nativeView = AdaptyUI.createNativePaywallView(
paywall = paywall,
observer = myPaywallObserver,
customTimers = customTimers,
)
```
---
# File: kmp-handle-paywall-actions
---
---
title: "Responder a acciones de botones en el SDK de Kotlin Multiplatform"
description: "Gestiona las acciones de botones del paywall en Kotlin Multiplatform usando Adapty para una mejor monetización de tu app."
---
:::warning
**Solo las compras y restauraciones se gestionan automáticamente.** El resto de acciones de botones, como cerrar paywalls o abrir enlaces, requieren implementar las respuestas correspondientes en el código de la app.
:::
Si estás creando paywalls con el Paywall Builder de Adapty, es fundamental configurar los botones correctamente:
1. Añade un [botón en el Paywall Builder](paywall-buttons) y asígnale una acción existente o crea un ID de acción personalizado.
2. Escribe código en tu app para gestionar cada acción que hayas asignado.
Esta guía muestra cómo gestionar acciones personalizadas y predefinidas en tu código.
## Configurar AdaptyUIPaywallsEventsObserver \{#set-up-the-adaptyuipaywallseventsob server\}
Para gestionar las acciones del paywall, necesitas implementar la interfaz `AdaptyUIPaywallsEventsObserver` y configurarla con `AdaptyUI.setPaywallsEventsObserver()`. Esto debe hacerse al inicio del ciclo de vida de tu app, normalmente en tu actividad principal o en la inicialización de la app.
```kotlin
// In your app initialization
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())
```
## Cerrar paywalls \{#close-paywalls\}
Para añadir un botón que cierre tu paywall:
1. En el Paywall Builder, añade un botón y asígnale la acción **Close**.
2. En el código de tu app, implementa un manejador para la acción `close` que descarte el paywall.
:::info
En el SDK de Kotlin Multiplatform, `CloseAction` y `AndroidSystemBackAction` cierran el paywall por defecto. Sin embargo, puedes sobreescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría abrir otro.
:::
```kotlin
class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
when (action) {
AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss()
}
}
}
// Set up the observer
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())
```
Si estás usando [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform), llamar a `view.dismiss()` no tiene ningún efecto — la vista está integrada en tu layout, no presentada a través del stack de KMP. En su lugar, elimina la vista de tu layout y llama a `dispose()` sobre ella.
## Abrir URLs desde paywalls \{#open-urls-from-paywalls\}
:::tip
Si quieres añadir un grupo de enlaces (por ejemplo, términos de uso y restauración de compras), añade un elemento **Link** en el Paywall Builder y gestiónalo igual que los botones con la acción **Open URL**.
:::
Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Términos de uso** o **Política de privacidad**):
1. En el Paywall Builder, añade un botón, asígnale la acción **Open URL** e introduce la URL que quieres abrir.
2. En el código de tu app, implementa un manejador para la acción `openUrl` que abra la URL recibida en un navegador.
:::info
En el SDK de Kotlin Multiplatform, `OpenUrlAction` proporciona la URL que debe abrirse. Puedes implementar lógica personalizada para gestionar la apertura de URLs, como mostrar un diálogo de confirmación o usar el método de gestión de URLs preferido de tu app.
:::
```kotlin
class MyAdaptyUIPaywallsEventsObserver(
private val uriHandler: UriHandler
) : AdaptyUIPaywallsEventsObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
when (action) {
is AdaptyUIAction.OpenUrlAction -> {
// Show confirmation dialog before opening URL
mainUiScope.launch {
val selectedAction = view.showDialog(
title = "Open URL?",
content = action.url,
primaryActionTitle = "Cancel",
secondaryActionTitle = "Open"
).getOrNull()
when (selectedAction) {
AdaptyUIDialogActionType.PRIMARY -> {
// User cancelled
}
AdaptyUIDialogActionType.SECONDARY -> {
// User confirmed - open URL
uriHandler.openUri(action.url)
}
else -> Unit
}
}
}
}
}
}
// Set up the observer with UriHandler
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver(uriHandler))
```
## Iniciar sesión en la app \{#log-into-the-app\}
Para añadir un botón que permita a los usuarios iniciar sesión en tu app:
1. En el Paywall Builder, añade un botón y asígnale una acción **Custom** con el ID "login".
2. En el código de tu app, implementa un manejador para la acción personalizada que identifique a tu usuario.
```kotlin
class MyAdaptyUIObserver : AdaptyUIObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) {
when (action) {
is AdaptyUIAction.CustomAction -> {
if (action.action == "login") {
// Handle login action - navigate to login screen
// This depends on your app's navigation system
// For example, in Compose Multiplatform:
// navController.navigate("login")
}
}
}
}
}
```
## Gestionar acciones personalizadas \{#handle-custom-actions\}
Para añadir un botón que gestione cualquier otra acción:
1. En el Paywall Builder, añade un botón, asígnale la acción **Custom** y asígnale un ID.
2. En el código de tu app, implementa un manejador para el ID de acción que hayas creado.
Por ejemplo, si tienes otro conjunto de ofertas de suscripción o compras únicas, puedes añadir un botón que muestre otro paywall:
```kotlin
class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
when (action) {
is AdaptyUIAction.CustomAction -> {
when (action.action) {
"login" -> {
// Handle login action - navigate to login screen
// This depends on your app's navigation system
// For example, in Compose Multiplatform:
// navController.navigate("login")
}
}
}
}
}
}
// Set up the observer
AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())
```
---
# File: kmp-handling-events
---
---
title: "Kotlin Multiplatform - Gestionar eventos del paywall"
description: "Gestiona eficientemente los eventos de suscripción en Kotlin Multiplatform con las herramientas de seguimiento de eventos de Adapty."
---
Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder) no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede reaccionar. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de producto, etc.) y notificaciones sobre acciones relacionadas con compras en el paywall. A continuación aprenderás cómo responder a estos eventos.
:::warning
Esta guía es solo para paywalls del **nuevo Paywall Builder**.
:::
Para controlar o monitorizar los procesos que ocurren en la pantalla del paywall dentro de tu app, implementa los métodos de la interfaz `AdaptyUIPaywallsEventsObserver`. Algunos métodos tienen implementaciones por defecto que gestionan automáticamente los escenarios más comunes.
:::note
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 \{#user-generated-events\}
### Aparición y desaparición del paywall \{#paywall-appearance-and-disappearance\}
Cuando un paywall aparece o desaparece, se invocan estos métodos:
```kotlin showLineNumbers title="Kotlin"
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
}
```
:::note
- En iOS, `paywallViewDidAppear` también se invoca cuando el usuario pulsa el [botón de web paywall](web-paywall#step-2a-add-a-web-purchase-button) dentro de un paywall y se abre un web paywall en un navegador in-app.
- En iOS, `paywallViewDidDisappear` también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador in-app desaparece de la pantalla.
:::
Ejemplos de eventos (haz clic para expandir)
```javascript
// Paywall appeared
{
// No additional data
}
// Paywall disappeared
{
// No additional data
}
```
### Selección de producto \{#product-selection\}
Si el usuario selecciona un producto para comprarlo, se invoca este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"productId": "premium_monthly"
}
```
### Inicio de compra \{#started-purchase\}
Si el usuario inicia el proceso de compra, se invoca este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"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 \{#successful-canceled-or-pending-purchase\}
Si una compra tiene éxito, se invoca este método. Por defecto, cierra automáticamente el paywall a menos que el usuario haya cancelado la compra:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
// 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"
}
}
```
Te recomendamos cerrar la pantalla del paywall cuando la compra sea exitosa.
### Compra fallida \{#failed-purchase\}
Si una compra falla por un error, se invoca este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos invá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.
```kotlin showLineNumbers title="Kotlin"
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 (haz clic para expandir)
```javascript
{
"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"
}
}
}
```
### Inicio de restauración \{#started-restore\}
Si el usuario inicia el proceso de restauración, se invoca este método:
```kotlin showLineNumbers title="Kotlin"
override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) {
// Handle restore start
// You can show loading indicators or track analytics here
}
```
### Restauración exitosa \{#successful-restore\}
Si la restauración de una compra tiene éxito, se invoca este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"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](subscription-status) para aprender cómo comprobarlo.
### Restauración fallida \{#failed-restore\}
Si `Adapty.restorePurchases()` falla, se invoca este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
```
### Finalización de la navegación de pago web \{#web-payment-navigation-completion\}
Si el usuario inicia el proceso de compra mediante un [web paywall](web-paywall), se invoca este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
// 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"
}
}
}
```
## Carga de datos y renderizado \{#data-fetching-and-rendering\}
### Errores de carga de productos \{#product-loading-errors\}
Si no pasas los productos durante la inicialización, AdaptyUI obtendrá los objetos necesarios del servidor por sí mismo. Si esta operación falla, AdaptyUI notificará el error llamando a este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}
```
### Errores de renderizado \{#rendering-errors\}
Si ocurre un error durante el renderizado de la interfaz, se notificará mediante este método:
```kotlin showLineNumbers title="Kotlin"
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)
```javascript
{
"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 producirse, así que si te encuentras con alguno, por favor comunícanoslo.
---
# File: kmp-use-fallback-paywalls
---
---
title: "Kotlin Multiplatform - Usar paywalls de respaldo"
description: "Gestiona los casos en que los usuarios están sin conexión o los servidores de Adapty no están disponibles"
---
Para mantener una experiencia de usuario fluida, es importante configurar [respaldos](/fallback-paywalls) para tus [paywalls](paywalls) y [onboardings](onboardings). Esta precaución amplía las capacidades de la aplicación en caso de pérdida parcial o total de la conexión a internet.
* **Si la aplicación no puede acceder a los servidores de Adapty:**
Podrá mostrar un paywall de respaldo y acceder a la configuración local del onboarding.
* **Si la aplicación no puede acceder a internet:**
Podrá mostrar un paywall de respaldo. Los onboardings incluyen contenido remoto y requieren conexión a internet para funcionar.
:::important
Antes de seguir los pasos de esta guía, [descarga](/local-fallback-paywalls) los archivos de configuración de respaldo desde Adapty.
:::
## Configuración \{#configuration\}
1. Añade el archivo de configuración de respaldo a tu aplicación.
* Si tu plataforma de destino es Android, mueve el archivo de configuración de respaldo a la carpeta `android/app/src/main/assets/`.
* Si tu plataforma de destino es iOS, añade el archivo JSON de respaldo al bundle de tu proyecto. (**File** -> **Add Files to YourProjectName**)
2. Llama al método `.setFallback` **antes** de obtener el paywall o onboarding de destino.
3. Establece el parámetro `assetId` según tu plataforma de destino.
* Android: usa la ruta del archivo relativa al directorio `assets`.
* iOS: usa el nombre completo del archivo.
```kotlin showLineNumbers
Adapty.setFallback(assetId = "fallback.json")
.onSuccess {
// Fallback paywalls loaded successfully
}
.onError { error ->
// Handle the error
}
```
Parámetros:
| Parámetro | Descripción |
| :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **assetId** | Nombre del archivo de configuración de respaldo (iOS).
Ruta del archivo de configuración de respaldo, relativa al directorio `assets` (Android). |
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
---
# File: kmp-localizations-and-locale-codes
---
---
title: "Usar localizaciones y códigos de idioma en el SDK de Kotlin Multiplatform"
description: "Gestiona las localizaciones y códigos de idioma de tu app para llegar a una audiencia global en tu aplicación Kotlin Multiplatform."
---
## Por qué esto es importante \{#why-this-is-important\}
Hay varios escenarios en los que los códigos de idioma entran en juego — por ejemplo, cuando intentas obtener el paywall correcto para la localización actual de tu app.
Como los códigos de idioma son complejos y pueden variar de una plataforma a otra, nos basamos en un estándar interno para todas las plataformas que soportamos. Sin embargo, precisamente por esa complejidad, es muy importante que entiendas exactamente qué estás enviando a nuestro servidor para obtener la localización correcta y qué ocurre después — así siempre recibirás lo que esperas.
## Estándar de códigos de idioma en Adapty \{#locale-code-standard-at-adapty\}
Para los códigos de idioma, Adapty utiliza una versión ligeramente modificada del [estándar BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): cada código está formado por subetiquetas en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués de Brasil), `zh` (chino simplificado), `zh-hant` (chino tradicional).
## Coincidencia de códigos de idioma \{#locale-code-matching\}
Cuando Adapty recibe una llamada del SDK con el código de idioma y empieza a buscar la localización correspondiente de un paywall, ocurre lo siguiente:
1. La cadena de idioma recibida se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`)
2. Se busca la localización cuyo código de idioma coincida exactamente
3. Si no se encuentra ninguna coincidencia, se toma la subcadena antes del primer guión (`pt` en el caso de `pt-br`) y se busca la localización correspondiente
4. Si tampoco se encuentra coincidencia, se devuelve la localización predeterminada `en`
De este modo, un dispositivo iOS que envió `'pt_BR'`, un dispositivo Android que envió `pt-BR`, y otro dispositivo que envió `pt-br` obtendrán el mismo resultado.
## Implementación de localizaciones: forma recomendada \{#implementing-localizations-recommended-way\}
Si te estás preguntando sobre las localizaciones, lo más probable es que ya estés gestionando recursos de cadenas localizadas en tu proyecto. Si ese es el caso, te recomendamos incluir un par clave-valor con el código de locale de Adapty correspondiente en cada uno de tus archivos de recursos para las localizaciones que uses. Luego, extrae el valor de esa clave al llamar a nuestro SDK, así:
```kotlin showLineNumbers
// 1. Add the Adapty locale code to your Compose Multiplatform resources
/*
composeResources/values/strings.xml (default — English)
*/
en
/*
composeResources/values-es/strings.xml (Spanish)
*/
es
/*
composeResources/values-pt-rBR/strings.xml (Portuguese — Brazil)
*/
pt-br
// 2. Extract and use the locale code
suspend fun fetchPaywall() {
val locale = getString(Res.string.adapty_paywalls_locale)
Adapty.getPaywall(
placementId = "YOUR_PLACEMENT_ID",
locale = locale
).onSuccess { paywall ->
// el paywall solicitado
}.onError { error ->
// gestionar el error
}
}
```
De este modo, tienes control total sobre qué localización se recuperará para cada usuario de tu app.
Si no usas recursos de Compose Multiplatform, la misma idea aplica a cualquier biblioteca de localización que uses (por ejemplo, [moko-resources](https://github.com/icerockdev/moko-resources)): guarda el código de idioma de Adapty como una cadena en el bundle de recursos de cada idioma y léelo antes de llamar al SDK.
## Otra forma de implementar las localizaciones \{#implementing-localizations-the-other-way\}
Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente códigos de idioma para cada localización. Esto implica extraer el código de idioma directamente del dispositivo, lo que requiere declaraciones `expect`/`actual`, ya que no existe una API de idioma compartida en `commonMain`:
```kotlin showLineNumbers
// commonMain
expect fun currentLocaleTag(): String
// androidMain
actual fun currentLocaleTag(): String = Locale.getDefault().toLanguageTag()
// iosMain
actual fun currentLocaleTag(): String = NSLocale.currentLocale.localeIdentifier
// commonMain — pass the locale code to Adapty
suspend fun fetchPaywall() {
Adapty.getPaywall(
placementId = "YOUR_PLACEMENT_ID",
locale = currentLocaleTag()
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
}
```
Ten en cuenta que no recomendamos este enfoque por varias razones:
1. En iOS, el idioma preferido del usuario y el locale regional del dispositivo no son idénticos. `NSLocale.currentLocale.localeIdentifier` devuelve el locale regional, que puede diferir del idioma en que los usuarios leen tu app. Las apps iOS que usan archivos de cadenas localizadas dependen de la lógica de resolución de Apple para combinar ambos, lo que funciona de forma automática con el enfoque recomendado anteriormente.
2. Es difícil predecir exactamente qué devolverá el dispositivo y si coincide con una localización de Adapty. El locale del dispositivo puede incluir extensiones o códigos regionales que no hayas configurado en Adapty; en ese caso, el SDK recurre a la coincidencia del primer subtag o, en último término, a `en`.
Igualmente, si decides usar este enfoque de todas formas, asegúrate de haber cubierto todos los casos de uso relevantes.
---
# File: kmp-web-paywalls
---
---
title: "Implementar web paywalls en el SDK de Kotlin Multiplatform"
description: "Configura un web paywall para recibir pagos sin las comisiones ni las revisiones de la store."
---
:::important
Antes de comenzar, asegúrate de haber [configurado tu web paywall en el dashboard](web-paywall) e instalado la versión 3.15 o posterior del SDK de Adapty.
:::
## Abrir web paywalls \{#open-web-paywalls\}
Si estás trabajando con un paywall que desarrollaste tú mismo, necesitas gestionar los web paywalls usando el método del SDK. El método `openWebPaywall`:
1. Genera una URL única que permite a Adapty vincular un paywall específico mostrado a un usuario concreto con la página web a la que es redirigido.
2. Detecta cuándo los usuarios regresan a la app y luego solicita `getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil han sido actualizados.
De esta forma, si el pago fue exitoso y los derechos de acceso han sido actualizados, la suscripción se activa en la app casi de inmediato.
:::note
Cuando los usuarios regresen a la app, actualiza la interfaz para reflejar los cambios del perfil. Adapty recibirá y procesará los eventos de actualización del perfil.
:::
```kotlin showLineNumbers
viewModelScope.launch {
Adapty.openWebPaywall(product = product).onSuccess {
// the web paywall was opened successfully
}.onError { error ->
// handle the error
}
}
```
:::note
Existen dos versiones del método `openWebPaywall`:
1. `openWebPaywall(product = product)`, que genera URLs a partir del paywall y también añade los datos del producto a las URLs.
2. `openWebPaywall(paywall = paywall)`, que genera URLs a partir del paywall sin añadir los datos del producto a las URLs. Úsalo cuando los productos de tu paywall en Adapty sean diferentes a los del web paywall.
:::
## Abrir web paywalls en un navegador integrado \{#open-web-paywalls-in-an-in-app-browser\}
Por defecto, los web paywalls se abren en el navegador externo.
Para ofrecer una experiencia de usuario más fluida, puedes abrir los web paywalls en un navegador integrado. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin salir de la app.
Para habilitarlo, establece el parámetro `openIn` en `AdaptyWebPresentation.IN_APP_BROWSER`:
```kotlin showLineNumbers
viewModelScope.launch {
Adapty.openWebPaywall(
product = product,
openIn = AdaptyWebPresentation.IN_APP_BROWSER // default – EXTERNAL_BROWSER
).onSuccess {
// the web paywall was opened successfully
}.onError { error ->
// handle the error
}
}
```
---
# File: kmp-troubleshoot-paywall-builder
---
---
title: "Solucionar problemas del Paywall Builder en el SDK de Kotlin Multiplatform"
description: "Solucionar problemas del Paywall Builder en el SDK de Kotlin Multiplatform"
---
Esta guía te ayuda a resolver problemas comunes al usar paywalls diseñados en el Adapty Paywall Builder en el SDK de Kotlin Multiplatform.
## Falla al obtener la configuración de un paywall \{#getting-a-paywall-configuration-fails\}
**Problema**: El método `createPaywallView` no consigue crear una vista del paywall, o el paywall no tiene una configuración de vista.
**Motivo**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder.
**Solución**: Activa el botón **Show on device** en el Paywall Builder. También puedes comprobar si un paywall tiene configuración de vista usando la propiedad `hasViewConfiguration` en el objeto `AdaptyPaywall`.
## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\}
**Problema**: El contador de vistas del paywall muestra el doble del número esperado.
**Motivo**: Es posible que estés llamando a `logShowPaywall` en tu código, lo que duplica el contador de vistas si estás usando el Paywall Builder. Para paywalls diseñados con el Paywall Builder, las analíticas se registran automáticamente, por lo que no es necesario usar este método.
**Solución**: Asegúrate de no estar llamando a `logShowPaywall` en tu código si estás usando el Paywall Builder.
---
# File: kmp-implement-paywalls-manually
---
---
title: "Implement paywalls manually in Kotlin Multiplatform SDK"
description: "Learn how to implement paywalls manually in your Kotlin Multiplatform app with Adapty SDK."
---
## Accept purchases
If you are working with paywalls you've implemented yourself, you can delegate handling purchases to Adapty, using the `makePurchase` method. This way, we will handle all the user scenarios, and you will only need to handle the purchase results.
:::important
`makePurchase` works with products created in the Adapty dashboard. Make sure you configure products and ways to retrieve them in the dashboard by following the [quickstart guide](quickstart).
:::
## Observer mode
If you want to implement your own purchase handling logic from scratch, but still want to benefit from the advanced analytics in Adapty, you can use the observer mode.
:::important
Consider the observer mode limitations [here](observer-vs-full-mode).
:::
---
# File: kmp-quickstart-manual
---
---
title: "Habilitar compras en tu paywall personalizado con el SDK de Kotlin Multiplatform"
description: "Integra el SDK de Adapty en tus paywalls personalizados de Kotlin Multiplatform para habilitar compras in-app."
---
Esta guía describe cómo integrar Adapty en tus paywalls personalizados. Mantén el control total sobre la implementación del paywall mientras el SDK de Adapty obtiene los productos, gestiona las nuevas compras y restaura las anteriores.
:::important
**Esta guía es para desarrolladores que implementan paywalls personalizados.** Si quieres la forma más sencilla de habilitar compras, usa el [Paywall Builder de Adapty](kmp-quickstart-paywalls). Con el Paywall Builder, creas paywalls en un editor visual sin código, Adapty gestiona toda la lógica de compra automáticamente y puedes probar distintos diseños sin volver a publicar tu app.
:::
## Antes de empezar \{#before-you-start\}
### Configura los productos \{#set-up-products\}
Para habilitar las compras in-app, necesitas entender tres conceptos clave:
- [**Productos**](product) – cualquier cosa que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida)
- [**Paywalls**](paywalls) – configuraciones que definen qué productos ofrecer. En Adapty, los paywalls son la única forma de recuperar productos, pero este diseño te permite modificar productos, precios y ofertas sin tocar el código de tu app.
- [**Placements**](placements) – dónde y cuándo muestras paywalls en tu app (como `main`, `onboarding`, `settings`). Configuras paywalls para placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos paywalls a diferentes usuarios.
Asegúrate de entender estos conceptos aunque trabajes con tu paywall personalizado. En esencia, son solo tu forma de gestionar los productos que vendes en tu app.
Para implementar tu paywall personalizado, necesitarás crear un **paywall** y añadirlo a un **placement**. Esta configuración te permite recuperar tus productos. Para entender qué debes hacer en el dashboard, sigue la guía de inicio rápido [aquí](quickstart).
### Gestiona los usuarios \{#manage-users\}
Puedes trabajar con o sin autenticación de backend por tu parte.
Sin embargo, el SDK de Adapty gestiona de forma diferente los usuarios anónimos y los identificados. Lee la [guía de inicio rápido de identificación](kmp-quickstart-identify) para entender los detalles y asegurarte de que trabajas con los usuarios correctamente.
## Paso 1. Obtén los productos \{#step-1-get-products\}
Para recuperar los productos de tu paywall personalizado, necesitas:
1. Obtener el objeto `paywall` pasando el ID del [placement](placements) al método `getPaywall`.
2. Obtener el array de productos para este paywall usando el método `getPaywallProducts`.
```kotlin showLineNumbers
fun loadPaywall() {
Adapty.getPaywall(placementId = "YOUR_PLACEMENT_ID")
.onSuccess { paywall ->
Adapty.getPaywallProducts(paywall = paywall)
.onSuccess { products ->
// Use products to build your custom paywall UI
}
.onError { error ->
// Handle the error
}
}
.onError { error ->
// Handle the error
}
}
```
## Paso 2. Acepta las compras \{#step-2-accept-purchases\}
Cuando un usuario toca un producto en tu paywall personalizado, llama al método `makePurchase` con el producto seleccionado. Esto gestionará el flujo de compra y devolverá el perfil actualizado.
```kotlin showLineNumbers
fun purchaseProduct(product: AdaptyPaywallProduct) {
Adapty.makePurchase(product = product)
.onSuccess { purchaseResult ->
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// Purchase successful, profile updated
}
is AdaptyPurchaseResult.UserCanceled -> {
// User canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Purchase is pending (e.g., user will pay offline with cash)
}
}
}
.onError { error ->
// Handle the error
}
}
```
## Paso 3. Restaura las compras \{#step-3-restore-purchases\}
Los stores de apps exigen que todas las apps con suscripciones ofrezcan una forma de restaurar las compras.
Llama al método `restorePurchases` cuando el usuario toque el botón de restaurar. Esto sincronizará su historial de compras con Adapty y devolverá el perfil actualizado.
```kotlin showLineNumbers
fun restorePurchases() {
Adapty.restorePurchases()
.onSuccess { profile ->
// Restore successful, profile updated
}
.onError { error ->
// Handle the error
}
}
```
## Pasos siguientes \{#next-steps\}
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro [foro de soporte](https://adapty.featurebase.app/) donde encontrarás respuestas a preguntas frecuentes o podrás plantear las tuyas. ¡Nuestro equipo y la comunidad están aquí para ayudarte!
Tu paywall está listo para mostrarse en la app. Prueba tus compras en el [sandbox de App Store](test-purchases-in-sandbox) o en [Google Play Store](testing-on-android) para asegurarte de que puedes completar una compra de prueba desde el paywall. Para ver cómo funciona esto en una implementación lista para producción, consulta el [AppViewModel.kt](https://github.com/adaptyteam/AdaptySDK-KMP/blob/main/example/composeMultiplatformApp/composeApp/src/commonMain/kotlin/com/adapty/exampleapp/AppViewModel.kt) en nuestra app de ejemplo, que demuestra la gestión de compras con manejo adecuado de errores y gestión de estados.
A continuación, [comprueba si los usuarios han completado su compra](kmp-check-subscription-status) para determinar si mostrar el paywall o conceder acceso a las funciones de pago.
---
# File: fetch-paywalls-and-products-kmp
---
---
title: "Obtener paywalls y productos para paywalls de Remote Config en el SDK de Kotlin Multiplatform"
description: "Obtén paywalls y productos en el SDK de Kotlin Multiplatform de Adapty para mejorar la monetización de los usuarios."
---
Antes de mostrar Remote Config y paywalls personalizados, necesitas obtener la información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls de Remote Config y personalizados. Para obtener orientación sobre cómo obtener paywalls creados con Paywall Builder, consulta [Obtener paywalls del Paywall Builder y su configuración](kmp-get-pb-paywalls).
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
Antes de empezar a obtener paywalls y productos en tu app móvil (haz clic para expandir)
1. [Crea tus productos](create-product) en el Adapty Dashboard.
2. [Crea un paywall e incorpora los productos en él](create-paywall) en el Adapty Dashboard.
3. [Crea placements e incorpora tu paywall en el placement](create-placement) en el Adapty Dashboard.
4. [Instala el SDK de Adapty](sdk-installation-kotlin-multiplatform) en tu app móvil.
## Obtener información del paywall \{#fetch-paywall-information\}
En Adapty, un [producto](product) es una combinación de productos tanto de App Store como de Google Play. Estos productos multiplataforma se integran en paywalls, lo que te permite mostrarlos en placements específicos de la app móvil.
Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`.
:::important
**No hardcodees los IDs de productos.** El único ID que debes hardcodear es el ID del placement. Los paywalls se configuran de forma remota, por lo que el número de productos y las ofertas disponibles pueden cambiar en cualquier momento. Tu app debe gestionar estos cambios de forma dinámica: si hoy un paywall devuelve dos productos y mañana tres, muéstralos todos sin necesidad de cambios en el código.
:::
```kotlin showLineNumbers
Adapty.getPaywall(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
loadTimeout = 5.seconds
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
| Parámetro | Presencia | Descripción |
|---------|--------|-----------|
| **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta es para el idioma y la segunda para la región.
Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.
|
| **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | Por defecto, el SDK intentará cargar datos del servidor y devolverá datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que los usuarios siempre obtengan los datos más actualizados.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero tendrán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.
Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.
El SDK de Adapty almacena los paywalls en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](kmp-use-fallback-paywalls). También usamos CDN para obtener los paywalls más rápido y un servidor de respaldo independiente en caso de que la CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls mientras se asegura la fiabilidad incluso cuando la conexión a internet es escasa.
|
| **loadTimeout** | por defecto: 5 s | Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo de espera, se devolverán datos en caché o el fallback local.
Ten en cuenta que en casos raros este método puede superar ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por diferentes solicitudes internamente.
|
¡No hardcodees los IDs de productos! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como pruebas gratuitas) pueden cambiar con el tiempo. Asegúrate de que tu código gestione estos escenarios.
Por ejemplo, si inicialmente obtienes 2 productos, tu app debería mostrar esos 2 productos. Sin embargo, si más tarde obtienes 3 productos, tu app debería mostrar los 3 sin requerir ningún cambio en el código. Lo único que debes hardcodear es el ID del placement.
Parámetros de respuesta:
| Parámetro | Descripción |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Paywall | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) con: una lista de IDs de productos, el identificador del paywall, Remote Config y varias otras propiedades. |
## Obtener productos \{#fetch-products\}
Una vez que tienes el paywall, puedes consultar el array de productos que le corresponde:
```kotlin showLineNumbers
Adapty.getPaywallProducts(paywall).onSuccess { products ->
// the requested products
}.onError { error ->
// handle the error
}
```
Parámetros de respuesta:
| Parámetro | Descripción |
| :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Products | Lista de objetos [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y varias otras propiedades. |
Al implementar tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). A continuación se muestran las propiedades más usadas, pero consulta el documento enlazado para obtener información completa sobre todas las propiedades disponibles.
| Propiedad | Descripción |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Title** | Para mostrar el título del producto, usa `product.localizedTitle`. Ten en cuenta que la localización se basa en el país de la store seleccionado por el usuario, no en el idioma del dispositivo. |
| **Price** | Para mostrar una versión localizada del precio, usa `product.price.localizedString`. Esta localización se basa en la información de idioma del dispositivo. También puedes acceder al precio como número usando `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda correspondiente, usa `product.price.currencySymbol`. |
| **Subscription Period** | Para mostrar el período (por ejemplo, semana, mes, año, etc.), usa `product.subscriptionDetails?.localizedSubscriptionPeriod`. Esta localización se basa en el idioma del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscriptionDetails?.subscriptionPeriod`. Desde ahí puedes acceder al enum `unit` para obtener la duración (es decir, DAY, WEEK, MONTH, YEAR o UNKNOWN). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral, verías `MONTH` en la propiedad unit y `3` en la propiedad numberOfUnits. |
| **Introductory Offer** | Para mostrar un distintivo u otro indicador de que una suscripción incluye una oferta introductoria, consulta la propiedad `product.subscriptionDetails?.introductoryOfferPhases`. Esta es una lista que puede contener hasta dos fases de descuento: la fase de prueba gratuita y la fase de precio introductorio. Dentro de cada objeto de fase encontrarás las siguientes propiedades útiles:
• `paymentMode`: un enum con los valores `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` y `UNKNOWN`. Las pruebas gratuitas serán del tipo `FREE_TRIAL`.
• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.
• `localizedNumberOfPeriods`: una cadena localizada con el idioma del dispositivo que describe la duración de la oferta. Por ejemplo, una oferta de prueba de tres días muestra `3 days` en este campo.
• `subscriptionPeriod`: como alternativa, puedes obtener los detalles individuales del período de la oferta con esta propiedad. Funciona de la misma manera para las ofertas que la sección anterior.
• `localizedSubscriptionPeriod`: un período de suscripción formateado del descuento para el idioma del usuario. |
## Acelerar la obtención del paywall con el paywall de la audiencia predeterminada \{#speed-up-paywall-fetching-with-default-audience-paywall\}
Normalmente, los paywalls se obtienen casi al instante, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, en casos donde tienes numerosas audiencias y paywalls, y tus usuarios tienen una conexión a internet débil, obtener un paywall puede tardar más de lo deseado. En estas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia de usuario fluida en lugar de no mostrar ningún paywall.
Para solucionar esto, puedes usar el método `getPaywallForDefaultAudience`, que obtiene el paywall del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el paywall con el método `getPaywall`, como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-kmp#fetch-paywall-information) anterior.
:::warning
Por qué recomendamos usar `getPaywall`
El método `getPaywallForDefaultAudience` tiene algunas desventajas importantes:
- **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar paywalls diferentes para distintas versiones de la app (la actual y las futuras), puedes encontrar dificultades. Tendrás que diseñar paywalls que sean compatibles con la versión actual (legacy) o aceptar que los usuarios con la versión actual (legacy) puedan encontrarse con paywalls que no se renderizan correctamente.
- **Pérdida de targeting**: Todos los usuarios verán el mismo paywall diseñado para la audiencia **All Users**, lo que significa que pierdes el targeting personalizado (incluido el basado en países, atribución de marketing o tus propios atributos personalizados).
Si estás dispuesto a aceptar estas desventajas para beneficiarte de una obtención de paywalls más rápida, usa el método `getPaywallForDefaultAudience` como se indica a continuación. De lo contrario, utiliza el `getPaywall` descrito [arriba](fetch-paywalls-and-products-kmp#fetch-paywall-information).
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
| Parámetro | Presencia | Descripción |
|---------|--------|-----------|
| **placementId** | obligatorio | El identificador del [Placement](placements). Es el valor que especificaste al crear un placement en tu Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la [localización del paywall](add-remote-config-locale). Se espera que este parámetro sea un código de idioma compuesto por una o más subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta es para el idioma y la segunda para la región.
Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.
|
| **fetchPolicy** | por defecto: `AdaptyPaywallFetchPolicy.Default` | Por defecto, el SDK intentará cargar datos del servidor y devolverá datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que los usuarios siempre obtengan los datos más actualizados.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` para devolver datos en caché si existen. En este caso, los usuarios puede que no obtengan los datos más recientes, pero tendrán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar solicitudes de red.
Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se reinstala la app o mediante una limpieza manual.
|
---
# File: present-remote-config-paywalls-kmp
---
---
title: "Renderizar un paywall diseñado con Remote Config en el SDK de Kotlin Multiplatform"
description: "Descubre cómo presentar paywalls de Remote Config en el SDK de Adapty para Kotlin Multiplatform y personalizar la experiencia del usuario."
---
Si has personalizado un paywall usando Remote Config, necesitarás implementar el renderizado en el código de tu aplicación móvil para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se ve tu paywall. Te proporcionamos un método para obtener la configuración remota, dándote la autonomía para mostrar tu paywall personalizado configurado mediante Remote Config.
## Obtener el Remote Config del paywall y presentarlo \{#get-paywall-remote-config-and-present-it\}
Para obtener el Remote Config de un paywall, accede a la propiedad `remoteConfig` y extrae los valores necesarios.
```kotlin showLineNumbers
Adapty.getPaywall(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
loadTimeout = 5.seconds
).onSuccess { paywall ->
val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String
// use the remote config values
}.onError { error ->
// handle the error
}
```
En este punto, una vez que hayas recibido todos los valores necesarios, es el momento de renderizarlos y ensamblarlos en una página visualmente atractiva. Asegúrate de que el diseño se adapte a las distintas pantallas y orientaciones de dispositivos móviles, ofreciendo una experiencia fluida y amigable en todos los dispositivos.
:::warning
Asegúrate de [registrar el evento de visualización del paywall](present-remote-config-paywalls-kmp#track-paywall-view-events) como se describe a continuación, para que los análisis de Adapty puedan capturar información para los embudos y las pruebas A/B.
:::
Cuando hayas terminado de mostrar el paywall, continúa configurando el flujo de compra. Cuando el usuario realice una compra, simplemente llama a `.makePurchase()` con el producto de tu paywall. Para más detalles sobre el método `.makePurchase()`, consulta [Realizar compras](kmp-making-purchases).
Te recomendamos [crear un paywall de respaldo llamado fallback paywall](kmp-use-fallback-paywalls). Este respaldo se mostrará al usuario cuando no haya conexión a internet ni caché disponible, garantizando una experiencia fluida incluso en esas situaciones.
## Registrar eventos de visualización del paywall \{#track-paywall-view-events\}
Adapty te ayuda a medir el rendimiento de tus paywalls. Aunque los datos de compras se recopilan automáticamente, el registro de las visualizaciones de paywalls requiere tu intervención, ya que solo tú sabes cuándo un cliente ve un paywall.
Para registrar un evento de visualización de paywall, simplemente llama a `.logShowPaywall(paywall)` y se reflejará en las métricas de tu paywall en los embudos y las pruebas A/B.
:::important
No es necesario llamar a `.logShowPaywall(paywall)` si estás mostrando paywalls creados en el [Paywall Builder](adapty-paywall-builder).
:::
```kotlin showLineNumbers
Adapty.logShowPaywall(paywall = paywall)
.onSuccess {
// paywall view logged successfully
}
.onError { error ->
// handle the error
}
```
Parámetros de la solicitud:
| Parámetro | Presencia | Descripción |
| :---------- | :------- |:-------------------------------------------------------------------------------------------------------|
| **paywall** | obligatorio | Un objeto [`AdaptyPaywall`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). |
---
# File: kmp-making-purchases
---
---
title: "Realizar compras en app móvil con el SDK de Kotlin Multiplatform"
description: "Guía para gestionar compras in-app y suscripciones con Adapty."
---
Mostrar paywalls dentro de tu app es un paso fundamental para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, simplemente mostrar estos paywalls es suficiente para gestionar las compras solo si usas el [Paywall Builder](adapty-paywall-builder) para personalizar tus paywalls.
Si no usas el Paywall Builder, debes utilizar un método aparte llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método actúa como la puerta de entrada para que los usuarios interactúen con los paywalls y procedan con sus transacciones.
Si tu paywall tiene una oferta promocional activa para el producto que el usuario quiere comprar, Adapty la aplicará automáticamente en el momento de la compra.
:::warning
Ten en cuenta que la oferta introductoria solo se aplicará automáticamente si usas los paywalls configurados con el Paywall Builder.
En otros casos, necesitarás [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Saltarte este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría dar lugar a cobrar el precio completo a usuarios que sí son elegibles para una oferta introductoria.
:::
Asegúrate de haber [completado la configuración inicial](quickstart) sin saltarte ningún paso. Sin ella, no podemos validar las compras.
## Realizar una compra \{#make-purchase\}
:::note
**¿Usas el [Paywall Builder](adapty-paywall-builder)?** Las compras se procesan automáticamente; puedes saltarte este paso.
**¿Buscas una guía paso a paso?** Consulta la [guía de inicio rápido](kmp-implement-paywalls-manually) para instrucciones de implementación completas con todo el contexto.
:::
```kotlin showLineNumbers
Adapty.makePurchase(product = product).onSuccess { purchaseResult ->
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// Grant access to the paid features
}
}
is AdaptyPurchaseResult.UserCanceled -> {
// Handle the case where the user canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Handle deferred purchases (e.g., the user will pay offline with cash)
}
}
}.onError { error ->
// Handle the error
}
```
Parámetros de la solicitud:
| Parámetro | Presencia | Descripción |
| :---------- | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------|
| **Product** | obligatorio | Un objeto [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) obtenido del paywall. |
Parámetros de la respuesta:
| Parámetro | Descripción |
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Profile** | Si la solicitud fue exitosa, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) proporciona información completa sobre los niveles de acceso, suscripciones y compras únicas de un usuario dentro de la app.
Comprueba el estado del nivel de acceso para verificar si el usuario tiene el acceso requerido a la app.
|
:::warning
**Nota:** si aún usas una versión de StoreKit de Apple inferior a v2.0 y una versión del SDK de Adapty inferior a v.2.9.0, necesitas proporcionar el [secreto compartido de App Store de Apple](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Este método está actualmente obsoleto según Apple.
:::
## Cambiar suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\}
Cuando un usuario elige una nueva suscripción en lugar de renovar la actual, el comportamiento depende del store. En Google Play, la suscripción no se actualiza automáticamente. Tendrás que gestionar el cambio en el código de tu app como se describe a continuación.
Para reemplazar la suscripción por otra en Android, llama al método `.makePurchase()` con el parámetro adicional:
```kotlin showLineNumbers
val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters(
oldSubVendorProductId = "old_subscription_product_id",
replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE
)
val purchaseParams = AdaptyPurchaseParameters.Builder()
.setSubscriptionUpdateParams(subscriptionUpdateParams)
.build()
Adapty.makePurchase(
product = product,
parameters = purchaseParams
).onSuccess { purchaseResult ->
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// successful cross-grade
}
is AdaptyPurchaseResult.UserCanceled -> {
// user canceled the purchase flow
}
is AdaptyPurchaseResult.Pending -> {
// the purchase has not been finished yet, e.g. user will pay offline by cash
}
}
}.onError { error ->
// Handle the error
}
```
Parámetro adicional de la solicitud:
| Parámetro | Presencia | Descripción |
|:---------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **parameters** | opcional | Un objeto [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/) pasado a través de [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). |
Puedes leer más sobre las suscripciones y los modos de reemplazo en la documentación de Google para desarrolladores:
- [Acerca de los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
- [Recomendaciones de Google para los modos de reemplazo](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations)
- Modo de reemplazo [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Nota: este método solo está disponible para actualizaciones de suscripción. No se admiten cambios a un nivel inferior.
- Modo de reemplazo [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Nota: el cambio de suscripción real solo ocurrirá cuando finalice el período de facturación de la suscripción actual.
## Canjear códigos de oferta en iOS \{#redeem-offer-codes-in-ios\}
---
no_index: true
---
import Callout from '../../../components/Callout.astro';
Sobre los códigos de oferta
Los códigos de oferta te permiten dar descuentos o períodos de prueba gratuitos a usuarios concretos. A diferencia de las ofertas habituales, que se aplican de forma automática, los códigos de oferta se distribuyen fuera de la app — por email, redes sociales o materiales impresos. Los usuarios los canjean introduciendo el código en el App Store, accediendo a una URL de canje o a través de un diálogo dentro de la app.
Para configurar códigos de oferta, abre una suscripción en App Store Connect y ve a su sección **Offer Codes**. Puedes crear [tres tipos](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) de códigos de oferta:
- **Free** — la suscripción es gratuita durante un período determinado y la siguiente renovación se cobra al precio completo.
- **Pay as you go** — el usuario paga un precio reducido en cada ciclo de facturación durante un período determinado y, después, la suscripción se renueva al precio completo.
- **Pay up front** — el usuario paga un precio único reducido por toda la duración de la oferta y, después, la suscripción se renueva al precio completo.
No es necesario añadir los códigos de oferta a Adapty. Apple etiqueta cada transacción durante el período de la oferta con la categoría del código de oferta. Esto incluye el canje inicial y todas las renovaciones con descuento posteriores. Adapty detecta la etiqueta y registra cada transacción con la categoría de oferta `offer_code`. Cuando termina el período de oferta y la suscripción se renueva al precio completo, la etiqueta deja de estar presente. Puedes filtrar los análisis por el tipo de oferta **Offer Code** en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds).
#### Resolución de discrepancias en los ingresos \{#revenue-discrepancy-troubleshooting\}
Si observas que una transacción con código de oferta aparece en Adapty al precio completo del producto en lugar del precio reducido, verifica lo siguiente en App Store Connect:
- El código de oferta tiene los precios correctos configurados para todas las regiones donde los usuarios pueden canjearlo.
- El precio de la oferta está configurado para el país o región específica del usuario. Apple envía el precio regional en la transacción. Si no hay ningún precio regional configurado para la oferta, Apple puede enviar el precio completo del producto.
Puedes filtrar y verificar las transacciones con código de oferta en el [Adapty Dashboard](controls-filters-grouping-compare-proceeds) mediante los filtros de tipo de oferta **Offer Code** y **Offer Discount Type**.
#### Códigos promocionales heredados (obsoletos) \{#legacy-promo-codes-deprecated\}
Apple dejó obsoletos los códigos promocionales para compras in-app en marzo de 2026. Los códigos de oferta los sustituyen con más funcionalidades: elegibilidad configurable, fechas de expiración y hasta 1 millón de códigos por trimestre. Si antes usabas códigos promocionales para compras in-app, migra a los códigos de oferta en App Store Connect.
Los códigos promocionales heredados (limitados a 100 por app y versión) daban acceso gratuito a una suscripción. A diferencia de los códigos de oferta, Apple no incluía información de descuento en las transacciones con código promocional — enviaba el precio completo del producto en el recibo. Por ello, Adapty registraba estas transacciones al precio completo, lo que generaba discrepancias entre los análisis de Adapty y App Store Connect.
Si ves transacciones históricas al precio completo que deberían haber sido gratuitas, es probable que provengan de códigos promocionales heredados. Como estos códigos ya están obsoletos, migra a los códigos de oferta para un seguimiento preciso de los ingresos.
Para mostrar la pantalla de canje de códigos en tu app:
```kotlin showLineNumbers
Adapty.presentCodeRedemptionSheet()
.onSuccess {
// code redemption sheet presented successfully
}
.onError { error ->
// handle the error
}
```
:::danger
Según nuestras observaciones, la pantalla de canje de códigos de oferta en algunas apps puede no funcionar de forma fiable. Recomendamos redirigir al usuario directamente a la App Store.
Para hacerlo, debes abrir la URL con el siguiente formato:
`https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}`
:::
## Gestionar planes de prepago (Android) \{#manage-prepaid-plans-android\}
Si los usuarios de tu app pueden comprar [planes de prepago](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (por ejemplo, comprar una suscripción no renovable por varios meses), puedes habilitar las [transacciones pendientes](https://developer.android.com/google/play/billing/subscriptions#pending) para planes de prepago.
```kotlin showLineNumbers
Adapty.activate(
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withGoogleEnablePendingPrepaidPlans(true)
.build()
).onSuccess {
// successful activation
}.onError { error ->
// handle the error
}
```
---
# File: kmp-restore-purchase
---
---
title: "Restaurar compras en la app móvil con el SDK de Kotlin Multiplatform"
description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida."
---
Restaurar compras es una función que permite a los usuarios recuperar el acceso a contenido comprado anteriormente, como suscripciones o compras in-app, sin que se les vuelva a cobrar. Esta función es especialmente útil para usuarios que pueden haber desinstalado y reinstalado la app, o que han cambiado a un nuevo dispositivo y quieren acceder a su contenido comprado previamente sin pagar de nuevo.
:::note
En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin necesidad de código adicional. Si ese es tu caso, puedes saltarte este paso.
:::
Para restaurar una compra si no usas el [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`:
```kotlin showLineNumbers
Adapty.restorePurchases().onSuccess { profile ->
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// successful access restore
}
}.onError { error ->
// handle the error
}
```
Parámetros de respuesta:
| Parámetro | Descripción |
|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Profile** | Un objeto [`AdaptyProfile`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-profile/). Este modelo contiene información sobre los niveles de acceso, suscripciones y compras no relacionadas con suscripciones.
Comprueba el **estado del nivel de acceso** para determinar si el usuario tiene acceso a la app.
|
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
---
# File: implement-observer-mode-kmp
---
---
title: "Implementar el modo Observer en el SDK de Kotlin Multiplatform"
description: "Implementa el modo Observer en Adapty para rastrear eventos de suscripción de usuarios en el SDK de Kotlin Multiplatform."
---
Si ya tienes tu propia infraestructura de compras y no estás listo para migrar completamente a Adapty, puedes explorar el [modo Observer](observer-vs-full-mode). En su forma básica, el modo Observer ofrece analíticas avanzadas e integración fluida con sistemas de atribución y analítica.
Si esto cubre tus necesidades, solo tienes que:
1. Activarlo al configurar el SDK de Adapty estableciendo el parámetro `observerMode` en `true`. Sigue las instrucciones de configuración para [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform).
2. [Reportar transacciones](report-transactions-observer-mode-kmp) desde tu infraestructura de compras existente a Adapty.
## Configuración del modo Observer \{#observer-mode-setup\}
Activa el modo Observer si gestionas las compras y el estado de las suscripciones por tu cuenta y usas Adapty para enviar eventos de suscripción y analíticas.
:::important
Cuando se ejecuta en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlo tú mismo.
:::
```kotlin showLineNumbers
val config = AdaptyConfig
.Builder("PUBLIC_SDK_KEY")
.withObserverMode(true) // default false
.build()
Adapty.activate(configuration = config)
.onSuccess {
Log.d("Adapty", "SDK initialised in observer mode")
}
.onError { error ->
Log.e("Adapty", "Adapty init error: ${error.message}")
}
```
Parámetros:
| Parámetro | Descripción |
| --------------------------- | ------------------------------------------------------------ |
| observerMode | Un valor booleano que controla el [modo Observer](observer-vs-full-mode). El valor predeterminado es `false`. |
## Usar paywalls de Adapty en el modo Observer \{#using-adapty-paywalls-in-observer-mode\}
Si también quieres usar las paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere una configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores:
1. Muestra los paywalls de la forma habitual para [paywalls con Remote Config](present-remote-config-paywalls-kmp).
3. [Asocia los paywalls](report-transactions-observer-mode-kmp) con las transacciones de compra.
---
# File: report-transactions-observer-mode-kmp
---
---
title: "Reportar transacciones en el modo Observer en el SDK de Kotlin Multiplatform"
description: "Reporta transacciones de compra en el modo Observer de Adapty para obtener información sobre usuarios y seguimiento de ingresos en el SDK de Kotlin Multiplatform."
---
En el modo Observer, el SDK de Adapty no puede rastrear por sí solo las compras realizadas a través de tu sistema de compras existente. Necesitas reportar las transacciones desde tu app store. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis.
Usa `reportTransaction` para reportar de forma explícita cada transacción y que Adapty pueda reconocerla.
:::warning
**¡No omitas el reporte de transacciones!**
Si no llamas a `reportTransaction`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones.
:::
Si usas paywalls de Adapty, incluye el `variationId` al reportar una transacción. Esto vincula la compra con el paywall que la originó, garantizando un análisis preciso del paywall.
```kotlin showLineNumbers
Adapty.reportTransaction(
transactionId = "your_transaction_id",
variationId = paywall.variationId
).onSuccess { profile ->
// Transaction reported successfully
// profile contains updated user data
}.onError { error ->
// handle the error
}
```
Parámetros:
| Parámetro | Presencia | Descripción |
| --------------- | ---------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| transactionId | obligatorio | El ID de transacción de tu compra en el app store. Normalmente es el token de compra o el identificador de transacción devuelto por el store. |
| variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). |
---
# File: kmp-troubleshoot-purchases
---
---
title: "Solucionar problemas de compras en el SDK de Kotlin Multiplatform"
description: "Solucionar problemas de compras en el SDK de Kotlin Multiplatform"
---
Esta guía te ayuda a resolver problemas comunes al implementar compras manualmente en el SDK de Kotlin Multiplatform.
## makePurchase se llama correctamente, pero el perfil no se actualiza \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\}
**Problema**: El método `makePurchase` se completa correctamente, pero el perfil del usuario y el estado de la suscripción no se actualizan en Adapty.
**Causa**: Esto normalmente indica una configuración incompleta de Google Play Store.
**Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android).
## makePurchase se invoca dos veces \{#makepurchase-is-invoked-twice\}
**Problema**: El método `makePurchase` se está llamando varias veces para la misma compra.
**Causa**: Esto suele ocurrir cuando el flujo de compra se activa varias veces debido a problemas de gestión del estado de la interfaz o por interacciones rápidas del usuario.
**Solución**: Asegúrate de haber completado todos los [pasos de configuración de Google Play](initial-android).
## AdaptyError.cantMakePayments en el modo observador \{#adaptye-rror-cantmakepayments-in-observer-mode\}
**Problema**: Estás recibiendo `AdaptyError.cantMakePayments` al usar `makePurchase` en el modo observador.
**Causa**: En el modo observador, debes gestionar las compras por tu cuenta, no usar el método `makePurchase` de Adapty.
**Solución**: Si usas `makePurchase` para las compras, desactiva el modo observador. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en el modo observador. Consulta [Implementar el modo observador](implement-observer-mode-kmp) para más detalles.
## Error de Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\}
**Problema**: Estás recibiendo un error de facturación no disponible de Google Play Store.
**Causa**: Este error no está relacionado con Adapty. Es un error de la biblioteca Google Play Billing que indica que la facturación no está disponible en el dispositivo.
**Solución**: Este error no está relacionado con Adapty. Puedes consultarlo en la documentación de Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers.
## No se encuentran makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\}
**Problema**: Estás teniendo problemas porque no se encuentran los `makePurchasesCompletionHandlers`.
**Causa**: Esto suele estar relacionado con problemas en las pruebas en sandbox.
**Solución**: Crea un nuevo usuario de sandbox e inténtalo de nuevo. Esto suele resolver los problemas con los manejadores de finalización de compras en sandbox.
---
# File: kmp-user
---
---
title: "Users & access in Kotlin Multiplatform SDK"
description: "Learn how to work with users and access levels in your Kotlin Multiplatform app with Adapty SDK."
---
This page contains all guides for working with users and access levels in your Kotlin Multiplatform app. Choose the topic you need:
- **[Identify users](kmp-identifying-users)** - Learn how to identify users in your app
- **[Update user data](kmp-setting-user-attributes)** - Set user attributes and profile data
- **[Listen for subscription status changes](kmp-listen-subscription-changes)** - Monitor subscription changes in real-time
- **[Kids Mode](kids-mode-kmp)** - Implement Kids Mode for your app
---
# File: kmp-identifying-users
---
---
title: "Identificar usuarios en Kotlin Multiplatform SDK"
description: "Identifica usuarios en Adapty para mejorar las experiencias de suscripción personalizadas."
---
Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, deberías establecer tu propio Customer User ID. Puedes encontrar usuarios por su Customer User ID en la sección [Perfiles](profiles-crm) y usarlo en la [API del lado del servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones.
### Configurar el ID de usuario del cliente durante la configuración \{#setting-customer-user-id-on-configuration\}
Si tienes un ID de usuario durante la configuración, pásalo como parámetro `customerUserId` al método `.activate()`:
```kotlin showLineNumbers
Adapty.activate(
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("YOUR_USER_ID")
.build()
).onSuccess {
// successful activation
}.onError { error ->
// handle the error
}
}
```
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
### Configuración del ID de usuario tras la inicialización \{#setting-customer-user-id-after-configuration\}
Si no tienes un ID de usuario en la configuración del SDK, puedes establecerlo más adelante en cualquier momento con el método `.identify()`. Los casos más habituales para usar este método son tras el registro o la autenticación, cuando el usuario pasa de ser anónimo a estar autenticado.
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID").onSuccess {
// successful identify
}.onError { error ->
// handle the error
}
```
Parámetros de la solicitud:
- **Customer User ID** (obligatorio): un identificador de usuario de tipo string.
:::warning
Reenvío de datos significativos del usuario
En algunos casos, como cuando un usuario vuelve a iniciar sesión en su cuenta, los servidores de Adapty ya tienen información sobre ese usuario. En estos escenarios, el SDK de Adapty cambiará automáticamente para trabajar con el nuevo usuario. Si enviaste algún dato al usuario anónimo, como atributos personalizados o atribuciones de redes de terceros, deberás reenviar esos datos para el usuario identificado.
También es importante tener en cuenta que debes volver a solicitar todos los paywalls y productos después de identificar al usuario, ya que los datos del nuevo usuario pueden ser diferentes.
:::
### Cerrar sesión e iniciar sesión \{#logging-out-and-logging-in\}
Puedes cerrar la sesión del usuario en cualquier momento llamando al método `.logout()`:
```kotlin showLineNumbers
Adapty.logout().onSuccess {
// successful logout
}.onError { error ->
// handle the error
}
```
Después puedes iniciar sesión con el método `.identify()`.
## Asignar `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\}
[`iosAppAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones del App Store con la identidad interna de tus usuarios.
StoreKit asocia este token con cada transacción, de modo que tu backend puede relacionar los datos del App Store con tus usuarios.
Usa un UUID estable generado por usuario y reutilízalo para la misma cuenta en todos los dispositivos.
Esto garantiza que las compras y las notificaciones del App Store queden correctamente vinculadas.
Puedes establecer el token de dos formas: durante la activación del SDK o al identificar al usuario.
:::important
Siempre debes pasar `iosAppAccountToken` junto con `customerUserId`.
Si solo pasas el token, no se incluirá en la transacción.
:::
```kotlin showLineNumbers
// Durante la configuración:
Adapty.activate(
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId(
id = "YOUR_USER_ID",
iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN"
)
.build()
).onSuccess {
// activación exitosa
}.onError { error ->
// manejar el error
}
// O al identificar usuarios
Adapty.identify(
customerUserId = "YOUR_USER_ID",
iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN"
).onSuccess {
// identificación exitosa
}.onError { error ->
// manejar el error
}
```
## Establecer IDs de cuenta ofuscados (Android) \{#set-obfuscated-account-ids-android\}
Google Play requiere IDs de cuenta ofuscados en ciertos casos de uso para mejorar la privacidad y seguridad del usuario. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, algo especialmente importante para la prevención de fraudes y los análisis.
Es posible que necesites configurar estos IDs si tu aplicación maneja datos sensibles de usuarios o si debes cumplir con regulaciones de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios.
:::important
Siempre debes pasar `androidObfuscatedAccountId` junto con `customerUserId`.
Si solo pasas el ID de cuenta ofuscado, no se incluirá en la transacción.
:::
```kotlin showLineNumbers
// Durante la configuración:
Adapty.activate(
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId(
id = "YOUR_USER_ID",
androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID"
)
.build()
).onSuccess {
// activación correcta
}.onError { error ->
// gestionar el error
}
// O al identificar usuarios
Adapty.identify(
customerUserId = "YOUR_USER_ID",
androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID"
).onSuccess {
// identificación correcta
}.onError { error ->
// gestionar el error
}
```
## Detectar usuarios en varios dispositivos \{#detect-users-across-devices\}
---
no_index: true
---
Cuando el SDK se activa, lee automáticamente los derechos existentes del usuario desde StoreKit (iOS) o Google Play Billing (Android) y los sincroniza con el backend de Adapty. Una suscripción activa aparece en el perfil de Adapty sin que la app llame a `restorePurchases`.
Lo que **no** ocurre automáticamente es reconocer que un perfil en un dispositivo nuevo pertenece al mismo usuario que el perfil en el dispositivo original. Adapty relaciona perfiles por Customer User ID, así que la continuidad de identidad depende de lo que uses como CUID.
**Lo que Adapty puede detectar entre dispositivos**
| Tu configuración | Lo que Adapty detecta | Lo que debes hacer |
| --- | --- | --- |
| Customer User ID = `device_id` (sin login en la app) | El nuevo dispositivo obtiene un CUID diferente y, por tanto, un perfil diferente. La suscripción se sincroniza con el nuevo perfil mediante un evento **Access level updated**, pero `subscription_started` no se dispara — el nuevo perfil se trata como heredero de la compra original. Los análisis basados en `subscription_started` contarán de menos a los usuarios que vuelven. | Usa un ID de cuenta estable como Customer User ID para que un usuario que regresa coincida con el perfil existente en todos los dispositivos. |
| Customer User ID = ID de cuenta estable (login en cada dispositivo) | El SDK sincroniza automáticamente la suscripción en `activate()`, e `identify()` relaciona el perfil existente por CUID. | No se necesita configuración adicional — tanto la identidad como la suscripción se resuelven automáticamente. |
| Heredero de Apple Family Sharing | El miembro de la familia recibe la suscripción solo a través de un evento **Access level updated** — `subscription_started` no se dispara. | Escucha el evento **Access level updated**. Consulta [Apple Family Sharing](apple-family-sharing) para ver la matriz de eventos completa. |
| Misma cuenta de Apple/Google, distintos usuarios dentro de la app | El primer perfil que registra la compra se convierte en el principal. Los perfiles posteriores ven la suscripción a través de una cadena de herederos, con un evento **Access level updated**. | Exige login y elige un [modo de compartición](sharing-paid-access-between-user-accounts) que se adapte a tu modelo. |
**Restaurar compras en un dispositivo nuevo**
Muestra un botón "Restaurar compras" iniciado por el usuario en tu paywall. Apple App Review (directriz 3.1.1) lo exige, y actúa como alternativa cuando la sincronización automática no cubre algún caso límite. El botón debe llamar a `restorePurchases` en tu SDK.
No es necesario llamar a `restorePurchases` de forma programática al primer inicio para el uso normal — el SDK ya ejecuta el equivalente en `activate()`. Reserva las llamadas programáticas para forzar una verificación de recibo actualizada, por ejemplo al depurar un acceso que falta después de que `activate()` haya completado.
---
# File: kmp-setting-user-attributes
---
---
title: "Establecer atributos de usuario en el SDK de Kotlin Multiplatform"
description: "Aprende cómo establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias."
---
Puedes establecer atributos opcionales como el correo electrónico, el número de teléfono, etc., en el usuario de tu aplicación. Luego puedes usar estos atributos para crear [segmentos](segments) de usuarios o simplemente verlos en el CRM.
### Establecer atributos de usuario \{#setting-user-attributes\}
Para establecer atributos de usuario, llama al método `.updateProfile()`:
```kotlin showLineNumbers
val builder = AdaptyProfileParameters.Builder()
.withEmail("email@email.com")
.withPhoneNumber("+18888888888")
.withFirstName("John")
.withLastName("Appleseed")
.withGender(AdaptyProfile.Gender.FEMALE)
.withBirthday(AdaptyProfile.Date(1970, 1, 3))
Adapty.updateProfile(builder.build())
.onSuccess {
// profile updated successfully
}
.onError { error ->
// handle the error
}
```
Ten en cuenta que los atributos que hayas establecido previamente con el método `updateProfile` no se restablecerán.
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
### Lista de claves permitidas \{#the-allowed-keys-list\}
Las claves permitidas `` de `AdaptyProfileParameters.Builder` y sus valores `` se muestran a continuación:
| Clave | Valor |
|---|-----|
| email
phoneNumber
firstName
lastName
| String |
| gender | Enum, los valores permitidos son: `AdaptyProfile.Gender.FEMALE`, `AdaptyProfile.Gender.MALE`, `AdaptyProfile.Gender.OTHER` |
| birthday | Date |
### Atributos personalizados de usuario \{#custom-user-attributes\}
Puedes definir tus propios atributos personalizados. Estos suelen estar relacionados con el uso de tu aplicación. Por ejemplo, en aplicaciones de fitness podrían ser el número de ejercicios por semana; en aplicaciones de aprendizaje de idiomas, el nivel de conocimiento del usuario, etc. Puedes usarlos en segmentos para crear paywalls y ofertas dirigidas, y también en análisis para determinar qué métricas de producto influyen más en los ingresos.
```kotlin showLineNumbers
val builder = AdaptyProfileParameters.Builder()
builder.withCustomAttribute("key1", "value1")
```
Para eliminar una clave existente, usa el método `.withRemovedCustomAttribute()`:
```kotlin showLineNumbers
val builder = AdaptyProfileParameters.Builder()
builder.withRemovedCustomAttribute("key2")
```
En ocasiones necesitas saber qué atributos personalizados ya se han establecido anteriormente. Para ello, utiliza el campo `customAttributes` del objeto `AdaptyProfile`.
:::warning
Ten en cuenta que el valor de `customAttributes` puede estar desactualizado, ya que los atributos de usuario pueden enviarse desde distintos dispositivos en cualquier momento, por lo que los atributos en el servidor pueden haber cambiado desde la última sincronización.
:::
### Límites \{#limits\}
- Hasta 30 atributos personalizados por usuario
- Los nombres de clave tienen un máximo de 30 caracteres. El nombre de la clave puede incluir caracteres alfanuméricos y cualquiera de los siguientes: `_` `-` `.`
- El valor puede ser una cadena de texto o un número flotante con un máximo de 50 caracteres.
---
# File: kmp-listen-subscription-changes
---
---
title: "Verificar el estado de la suscripción en el SDK de Kotlin Multiplatform"
description: "Rastrea y gestiona el estado de la suscripción de usuarios en Adapty para mejorar la retención de clientes en tu app de Kotlin Multiplatform."
---
Con Adapty, hacer seguimiento del estado de la suscripción es muy sencillo. No tienes que insertar manualmente IDs de productos en tu código. En su lugar, puedes confirmar fácilmente el estado de la suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo.
Antes de empezar a verificar el estado de la suscripción, configura las [notificaciones de desarrollador en tiempo real (RTDN)](enable-real-time-developer-notifications-rtdn).
## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptyprofile-object\}
Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). Te recomendamos obtener el perfil cuando tu app se inicie, por ejemplo al [identificar un usuario](android-identifying-users#setting-customer-user-id-on-configuration), y actualizarlo cada vez que se produzcan cambios. Así podrás usar el objeto de perfil sin tener que solicitarlo repetidamente.
Para recibir notificaciones sobre actualizaciones del perfil, escucha los cambios tal como se describe en la sección [Escuchar actualizaciones del perfil, incluidos los niveles de acceso](android-listen-subscription-changes) a continuación.
:::tip
¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras [apps de ejemplo](sample-apps), que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.
:::
## Obtener el nivel de acceso desde el servidor \{#retrieving-the-access-level-from-the-server\}
Para obtener el nivel de acceso desde el servidor, usa el método `.getProfile()`:
```kotlin showLineNumbers
Adapty.getProfile().onSuccess { profile ->
// check the access
}.onError { error ->
// handle the error
}
```
Parámetros de respuesta:
| Parámetro | Descripción |
| --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Profile | Un objeto [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). En general, solo tienes que comprobar el estado del nivel de acceso del perfil para determinar si el usuario tiene acceso premium a la app.
El método `.getProfile` proporciona el resultado más actualizado, ya que siempre intenta consultar la API. Si por alguna razón (por ejemplo, sin conexión a internet), el SDK de Adapty no puede obtener información del servidor, se devolverán los datos de la caché. También es importante tener en cuenta que el SDK de Adapty actualiza la caché de `AdaptyProfile` periódicamente para mantener esta información lo más actualizada posible.
|
El método `.getProfile()` te proporciona el perfil de usuario desde el que puedes obtener el estado del nivel de acceso. Puedes tener varios niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a distintos temas de forma independiente, puedes crear niveles de acceso "sports" y "science". Pero la mayoría de las veces solo necesitarás un nivel de acceso; en ese caso, puedes usar simplemente el nivel de acceso predeterminado "premium".
A continuación se muestra un ejemplo para comprobar el nivel de acceso predeterminado "premium":
```kotlin showLineNumbers
Adapty.getProfile().onSuccess { profile ->
if (profile.accessLevels["premium"]?.isActive == true) {
// grant access to premium features
}
}.onError { error ->
// handle the error
}
```
### Escuchar actualizaciones del estado de la suscripción \{#listening-for-subscription-status-updates\}
Cada vez que cambia la suscripción de un usuario, Adapty lanza un evento.
Para recibir mensajes de Adapty, necesitas realizar una configuración adicional:
```kotlin showLineNumbers
Adapty.setOnProfileUpdatedListener { profile ->
// handle any changes to subscription state
}
```
Adapty también lanza un evento al inicio de la aplicación. En ese caso, se pasará el estado de la suscripción almacenado en caché.
### Caché del estado de la suscripción \{#subscription-status-cache\}
La caché implementada en el SDK de Adapty almacena el estado de la suscripción del perfil. Esto significa que, aunque el servidor no esté disponible, se puede acceder a los datos en caché para obtener información sobre el estado de la suscripción del perfil.
Sin embargo, es importante tener en cuenta que no es posible solicitar datos directamente desde la caché. El SDK consulta periódicamente el servidor cada minuto para comprobar si hay actualizaciones o cambios relacionados con el perfil. Si hay alguna modificación, como nuevas transacciones u otras actualizaciones, se enviarán a los datos en caché para mantenerlos sincronizados con el servidor.
---
# File: kmp-deal-with-att
---
---
title: "Gestionar ATT en el SDK de Kotlin Multiplatform"
description: "Comienza con Adapty en Kotlin Multiplatform para simplificar la configuración y gestión de suscripciones."
---
Si tu aplicación usa el framework AppTrackingTransparency y muestra al usuario una solicitud de autorización de seguimiento, debes enviar el [estado de autorización](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) a Adapty.
```kotlin showLineNumbers
val profileParameters = AdaptyProfileParameters.Builder()
.withAttStatus(3) // 3 = ATTrackingManagerAuthorizationStatusAuthorized
.build()
Adapty.updateProfile(profileParameters)
.onSuccess {
// ATT status updated successfully
}
.onError { error ->
// handle AdaptyError
}
```
:::warning
Te recomendamos encarecidamente que envíes este valor lo antes posible cuando cambie; solo así los datos se transmitirán a tiempo a las integraciones que hayas configurado.
:::
---
# File: kids-mode-kmp
---
---
title: "Modo para Niños en el SDK de Kotlin Multiplatform"
description: "Activa fácilmente el Modo para Niños para cumplir con las políticas de Google. Sin GAID ni datos publicitarios recopilados en el SDK de Kotlin Multiplatform."
---
Si tu aplicación de Kotlin Multiplatform está destinada a niños, debes seguir las políticas de [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Si usas el SDK de Adapty, unos pocos pasos sencillos te ayudarán a configurarlo para cumplir con estas políticas y superar las revisiones de la app store.
## ¿Qué se requiere? \{#whats-required\}
Necesitas configurar el SDK de Adapty para deshabilitar la recopilación de:
- [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS)
- [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android)
- [Dirección IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf)
Además, te recomendamos usar el ID de usuario del cliente con cuidado. Un ID de usuario con el formato `` se considerará definitivamente como recopilación de datos personales, al igual que el uso del correo electrónico. Para el Modo para Niños, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs hasheados o UUIDs generados por el dispositivo) para garantizar el cumplimiento.
## Habilitar el Modo para Niños \{#enabling-kids-mode\}
### Cambios en el Adapty Dashboard \{#updates-in-the-adapty-dashboard\}
En el Adapty Dashboard, debes deshabilitar la recopilación de direcciones IP. Para ello, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** dentro de **Collect users' IP address**.
### Cambios en el código de tu aplicación móvil \{#updates-in-your-mobile-app-code\}
Para cumplir con las políticas, debes deshabilitar la recopilación del Android Advertising ID (AAID/GAID) y la dirección IP al inicializar el SDK de Adapty:
```kotlin showLineNumbers
override fun onCreate() {
super.onCreate()
val config = AdaptyConfig
.Builder("PUBLIC_SDK_KEY")
// highlight-start
.withGoogleAdvertisingIdCollectionDisabled(true) // set to `true`
.withIpAddressCollectionDisabled(true) // set to `true`
// highlight-end
.build()
Adapty.activate(configuration = config)
.onSuccess {
Log.d("Adapty", "SDK initialised with privacy settings")
}
.onError { error ->
Log.e("Adapty", "Adapty init error: ${error.message}")
}
}
```
---
# File: kmp-onboardings
---
---
title: "Onboardings in Kotlin Multiplatform SDK"
description: "Learn how to work with onboardings in your Kotlin Multiplatform app with Adapty SDK."
---
---
# File: kmp-get-onboardings
---
---
title: "Obtener onboardings en el SDK de Kotlin Multiplatform"
description: "Aprende cómo recuperar onboardings en Adapty para Kotlin Multiplatform."
---
Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el editor en el Adapty Dashboard, puedes mostrarlo en tu app de Kotlin Multiplatform. El primer paso es obtener el onboarding asociado al placement y su configuración de vista, como se describe a continuación.
Antes de comenzar, asegúrate de que:
1. Has instalado el [SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) versión 3.15.0 o superior.
2. Has [creado un onboarding](create-onboarding).
3. Has añadido el onboarding a un [placement](placements).
## Obtener el onboarding \{#fetch-onboarding\}
Cuando creas un [onboarding](onboardings) con nuestro editor sin código, se almacena como un contenedor con configuración que tu app necesita obtener y mostrar. Este contenedor gestiona toda la experiencia: qué contenido aparece, cómo se presenta y cómo se procesan las interacciones del usuario (como respuestas a cuestionarios o entradas de formularios). El contenedor también registra automáticamente los eventos de analíticas, por lo que no necesitas implementar un seguimiento de vistas por separado.
Para obtener el mejor rendimiento, solicita la configuración del onboarding con antelación para que las imágenes tengan tiempo suficiente de descargarse antes de mostrárselas a los usuarios.
Para obtener un onboarding, usa el método `getOnboarding`:
```kotlin showLineNumbers
Adapty.getOnboarding(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
loadTimeout = 5.seconds
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
Parámetros:
| Parámetro | Presencia | Descripción |
|---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil.
|
| **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, puede que los usuarios no reciban los datos más recientes, pero tendrán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.
Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.
El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es limitada.
|
| **loadTimeout** | por defecto: 5 seg | Este valor limita el tiempo de espera para este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el fallback local.
Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera un poco después de lo especificado en `loadTimeout`, ya que la operación puede estar compuesta de distintas solicitudes internamente.
|
Parámetros de respuesta:
| Parámetro | Descripción |
|:----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Onboarding | Un objeto [`AdaptyOnboarding`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-onboarding/) con: el identificador y la configuración del onboarding, el Remote Config y otras propiedades. |
## Acelerar la obtención del onboarding con el onboarding de audiencia predeterminada \{#speed-up-onboarding-fetching-with-default-audience-onboarding\}
Normalmente, los onboardings se obtienen casi de inmediato, por lo que no necesitas preocuparte por acelerar este proceso. Sin embargo, si tienes muchas audiencias y onboardings y tus usuarios tienen una conexión a internet débil, la obtención de un onboarding puede tardar más de lo deseado. En esas situaciones, puede que quieras mostrar un onboarding predeterminado para garantizar una experiencia fluida en lugar de no mostrar ninguno.
Para esto, puedes usar el método `getOnboardingForDefaultAudience`, que obtiene el onboarding del placement especificado para la audiencia **All Users**. Sin embargo, es fundamental entender que el enfoque recomendado es obtener el onboarding con el método `getOnboarding`, tal como se detalla en la sección [Obtener el onboarding](#fetch-onboarding) anterior.
:::warning
Considera usar `getOnboarding` en lugar de `getOnboardingForDefaultAudience`, ya que este último tiene limitaciones importantes:
- **Problemas de compatibilidad**: Puede generar problemas al mantener varias versiones de la app, lo que exige diseños retrocompatibles o asumir que versiones más antiguas podrían mostrarse incorrectamente.
- **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación por país, atribución o atributos personalizados.
Si la mayor velocidad de obtención compensa estos inconvenientes en tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` tal como se describe [arriba](#fetch-onboarding).
:::
```kotlin showLineNumbers
Adapty.getOnboardingForDefaultAudience(
placementId = "YOUR_PLACEMENT_ID",
locale = "en",
fetchPolicy = AdaptyPaywallFetchPolicy.Default,
).onSuccess { paywall ->
// the requested paywall
}.onError { error ->
// handle the error
}
```
Parámetros:
| Parámetro | Presencia | Descripción |
|---------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | obligatorio | El identificador del [Placement](placements) deseado. Es el valor que especificaste al crear un placement en el Adapty Dashboard. |
| **locale** | opcional
por defecto: `en`
| El identificador de la localización del onboarding. Se espera que este parámetro sea un código de idioma compuesto por uno o dos subetiquetas separadas por el carácter menos (**-**). La primera subetiqueta corresponde al idioma y la segunda a la región.
Ejemplo: `en` significa inglés, `pt-br` representa el portugués de Brasil. |
| **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` | Por defecto, el SDK intentará cargar los datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre obtengan los datos más actualizados.
Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En ese caso, puede que los usuarios no reciban los datos más recientes, pero tendrán tiempos de carga más rápidos independientemente de la calidad de su conexión. La caché se actualiza con regularidad, por lo que es seguro usarla durante la sesión para evitar peticiones de red.
Ten en cuenta que la caché permanece intacta al reiniciar la app y solo se borra cuando se desinstala la app o mediante una limpieza manual.
El SDK de Adapty almacena los onboardings localmente en dos capas: la caché de actualización periódica descrita anteriormente y los onboardings de respaldo. También usamos CDN para obtener los onboardings más rápido y un servidor de respaldo independiente en caso de que el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es limitada.
|
---
# File: kmp-present-onboardings
---
---
title: "Presentar onboardings en Kotlin Multiplatform SDK"
description: "Aprende a presentar onboardings de forma efectiva para impulsar más conversiones."
---
Si has personalizado un onboarding con el builder, no necesitas preocuparte por renderizarlo en el código de tu app Kotlin Multiplatform para mostrárselo al usuario. Ese onboarding incluye tanto lo que debe mostrarse como la forma en que debe hacerlo.
Antes de empezar, asegúrate de que:
1. Tienes instalado [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) 3.16.1 o posterior.
2. Has [creado un onboarding](create-onboarding).
3. Has añadido el onboarding a un [placement](placements).
El SDK de Adapty para Kotlin Multiplatform ofrece dos formas de presentar onboardings:
- **Con Compose Multiplatform**
- **Sin Compose Multiplatform**
## Con Compose Multiplatform \{#with-compose-multiplatform\}
Para mostrar un onboarding, usa el método `view.present()` sobre el `view` creado por el método `createOnboardingView`. Cada `view` solo puede usarse una vez. Si necesitas mostrar el onboarding de nuevo, llama a `createOnboardingView` otra vez para crear una nueva instancia de `view`.
:::warning
Reutilizar el mismo `view` sin recrearlo puede producir un error.
:::
```kotlin showLineNumbers title="Kotlin Multiplatform"
viewModelScope.launch {
AdaptyUI.createOnboardingView(onboarding = onboarding).onSuccess { view ->
view.present()
}.onError { error ->
// handle the error
}
}
```
### Configurar el estilo de presentación en iOS \{#configure-ios-presentation-style\}
Configura cómo se presenta el onboarding en iOS pasando el parámetro `iosPresentationStyle` al método `present()`. El parámetro acepta los valores `AdaptyUIIOSPresentationStyle.FULLSCREEN` (predeterminado) o `AdaptyUIIOSPresentationStyle.PAGESHEET`.
```kotlin showLineNumbers
viewModelScope.launch {
val view = AdaptyUI.createOnboardingView(onboarding = onboarding).getOrNull()
view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET)
}
```
### Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\}
Por defecto, los enlaces de los onboardings se abren en un navegador in-app. Esto ofrece una experiencia fluida al mostrar las páginas web dentro de tu aplicación, sin que el usuario tenga que cambiar de app.
Si prefieres que los enlaces se abran en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `AdaptyWebPresentation.EXTERNAL_BROWSER`:
```kotlin showLineNumbers
viewModelScope.launch {
AdaptyUI.createOnboardingView(
onboarding = onboarding,
externalUrlsPresentation = AdaptyWebPresentation.EXTERNAL_BROWSER // default – IN_APP_BROWSER
).onSuccess { view ->
view.present()
}.onError { error ->
// handle the error
}
}
```
## Sin Compose Multiplatform \{#without-compose-multiplatform\}
:::note
`createNativeOnboardingView` forma parte del módulo principal `io.adapty:adapty-kmp`. Si tu proyecto no usa Compose Multiplatform, no necesitas la dependencia `io.adapty:adapty-kmp-ui`.
:::
Para integrar un onboarding sin Compose Multiplatform, llama a `createNativeOnboardingView`. Devuelve un `AdaptyNativeOnboardingView` que añades a tu layout:
```kotlin showLineNumbers title="Kotlin Multiplatform (Android)"
val nativeView = AdaptyUI.createNativeOnboardingView(
context = context,
viewModelStoreOwner = activity,
onboarding = onboarding,
observer = myOnboardingObserver,
)
// Embed in your Compose layout:
AndroidView(
factory = { nativeView.view },
modifier = Modifier.fillMaxSize()
)
```
Dado que los métodos predeterminados de la interfaz KMP se convierten en `@required` en Swift, no puedes implementar `AdaptyUIOnboardingsEventsObserver` directamente desde Swift. Primero declara una clase base abierta en `iosMain`:
```kotlin showLineNumbers title="iosMain (Kotlin)"
open class BaseOnboardingObserver : AdaptyUIOnboardingsEventsObserver
```
Luego crea una subclase en Swift, sobreescribiendo solo lo que necesites:
```swift showLineNumbers title="Swift"
class MyOnboardingObserver: BaseOnboardingObserver {
override func onboardingViewOnCloseAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
actionId: String
) {
// remove nativeView from your view hierarchy
}
}
let nativeView = AdaptyUI.shared.createNativeOnboardingView(
onboarding: onboarding,
observer: MyOnboardingObserver()
)
// nativeView.viewController is a UIViewController.
// Add it to your SwiftUI view or UIKit hierarchy.
```
### Liberar la vista \{#dispose-the-view\}
Llama a `dispose()` cuando elimines la vista de tu layout. Esto cancela el registro del listener de eventos y libera los recursos internos.
```kotlin showLineNumbers title="Kotlin Multiplatform"
nativeView.dispose()
```
---
# File: kmp-handling-onboarding-events
---
---
title: "Gestionar eventos del onboarding en el SDK de Kotlin Multiplatform"
description: "Gestiona eventos relacionados con el onboarding en Kotlin Multiplatform usando Adapty."
---
Antes de empezar, asegúrate de que:
1. Has instalado el [SDK de Adapty para Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) 3.15.0 o posterior.
2. Has [creado un onboarding](create-onboarding).
3. Has añadido el onboarding a un [placement](placements).
Los onboardings configurados con el builder generan eventos a los que tu app puede responder. A continuación aprenderás cómo hacerlo.
## Configurar el observador de eventos del onboarding \{#set-up-the-onboarding-event-observer\}
Para gestionar los eventos del onboarding, necesitas implementar la interfaz `AdaptyUIOnboardingsEventsObserver` y configurarla con `AdaptyUI.setOnboardingsEventsObserver()`. Esto debe hacerse en una etapa temprana del ciclo de vida de tu app, normalmente en tu actividad principal o en la inicialización de la app.
```kotlin
// In your app initialization
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
## Acciones personalizadas \{#custom-actions\}
En el builder, puedes añadir una acción **custom** a un botón y asignarle un ID. Luego puedes usar ese ID en tu código y gestionarlo como una acción personalizada.
Por ejemplo, si un usuario pulsa un botón personalizado como **Login** o **Allow notifications**, se activará el método delegado `onCustomAction` con el ID de acción definido en el builder. Puedes crear tus propios IDs, como "allowNotifications".
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnCustomAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
actionId: String
) {
when (actionId) {
"openPaywall" -> {
// Display paywall from onboarding
// You would typically fetch and present a new paywall here
mainUiScope.launch {
// Example: Get paywall by placement ID
// val paywallResult = Adapty.getPaywall("your_placement_id")
// paywallResult.onSuccess { paywall ->
// val paywallViewResult = AdaptyUI.createPaywallView(paywall)
// paywallViewResult.onSuccess { paywallView ->
// paywallView.present()
// }
// }
}
}
"allowNotifications" -> {
// Handle notification permissions
}
else -> {
// Handle other custom actions
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplo de evento (clic para expandir)
```json
{
"actionId": "allowNotifications",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
}
}
```
## Cerrar el onboarding \{#closing-onboarding\}
El onboarding se considera cerrado cuando un usuario pulsa un botón con la acción **Close** asignada. Debes gestionar qué ocurre cuando el usuario cierra el onboarding. Por ejemplo:
:::important
Necesitas gestionar qué ocurre cuando un usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding.
:::
Si estás usando [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform), `view.isStandaloneView` es `false` — la implementación por defecto no llama a `view.dismiss()`. En su lugar, elimina la vista de tu layout y llama a `dispose()` en este callback.
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnCloseAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
actionId: String
) {
// Dismiss the onboarding screen
mainUiScope.launch {
view.dismiss()
}
// Additional cleanup or navigation logic can be added here
// For example, navigate back or show main app content
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplo de evento (clic para expandir)
```json
{
"action_id": "close_button",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "final_screen",
"screen_index": 3,
"total_screens": 4
}
}
```
## Abrir un paywall \{#opening-a-paywall\}
:::tip
Gestiona este evento para abrir un paywall si quieres mostrarlo dentro del onboarding. Si prefieres abrirlo una vez cerrado el onboarding, hay una forma más directa: gestiona [`onboardingViewOnCloseAction`](#closing-onboarding) y abre el paywall sin depender de los datos del evento.
:::
Si un usuario hace clic en un botón que abre un paywall, recibirás el ID de acción del botón que [configuraste manualmente](get-paid-in-onboardings). La forma más fluida de trabajar con paywalls dentro de onboardings es hacer que el ID de acción coincida con el ID de placement del paywall. Así puedes usar el ID del placement para obtener y abrir el paywall directamente:
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnPaywallAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
actionId: String
) {
// Get the paywall using the placement ID from the action
mainUiScope.launch {
val paywallResult = Adapty.getPaywall(placementId = actionId)
paywallResult.onSuccess { paywall ->
val paywallViewResult = AdaptyUI.createPaywallView(paywall)
paywallViewResult.onSuccess { paywallView ->
paywallView.present()
}.onError { error ->
// handle the error
}
}.onError { error ->
// handle the error
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplo de evento (clic para expandir)
```json
{
"action_id": "premium_offer_1",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "pricing_screen",
"screen_index": 2,
"total_screens": 4
}
}
```
## Finalizar la carga del onboarding \{#finishing-loading-onboarding\}
Cuando el onboarding termina de cargarse, se invocará este método:
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewDidFinishLoading(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta
) {
// Handle loading completion
// You can add any initialization logic here
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplo de evento (clic para expandir)
```json
{
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "welcome_screen",
"screen_index": 0,
"total_screens": 4
}
}
```
## Eventos de navegación \{#navigation-events\}
El método `onboardingViewOnAnalyticsEvent` se llama cuando ocurren distintos eventos de análisis durante el flujo del onboarding.
El objeto `event` puede ser uno de los siguientes tipos:
|Tipo | Descripción |
|------------|-------------|
| `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Cuando el onboarding ha sido cargado |
| `AdaptyOnboardingsAnalyticsEventScreenPresented` | Cuando se muestra cualquier pantalla |
| `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Cuando se completa una pantalla. Incluye `elementId` opcional (identificador del elemento completado) y `reply` opcional (respuesta del usuario). Se activa cuando los usuarios realizan cualquier acción para salir de la pantalla. |
| `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Cuando se muestra la segunda pantalla |
| `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Se activa cuando se recoge el correo electrónico del usuario mediante el campo de entrada |
| `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Se activa cuando un usuario llega a una pantalla con el ID `final`. Si necesitas este evento, asigna el ID `final` a la última pantalla. |
| `AdaptyOnboardingsAnalyticsEventUnknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) |
Cada evento incluye información `meta` con los siguientes campos:
| Campo | Descripción |
|------------|-------------|
| `onboardingId` | Identificador único del flujo de onboarding |
| `screenClientId` | Identificador de la pantalla actual |
| `screenIndex` | Posición de la pantalla actual en el flujo |
| `screensTotal` | Número total de pantallas en el flujo |
Aquí tienes un ejemplo de cómo usar los eventos de análisis para el seguimiento:
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnAnalyticsEvent(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
event: AdaptyOnboardingsAnalyticsEvent
) {
when (event) {
is AdaptyOnboardingsAnalyticsEventOnboardingStarted -> {
// Track onboarding start
trackEvent("onboarding_started", event.meta)
}
is AdaptyOnboardingsAnalyticsEventScreenPresented -> {
// Track screen presentation
trackEvent("screen_presented", event.meta)
}
is AdaptyOnboardingsAnalyticsEventScreenCompleted -> {
// Track screen completion with user response
trackEvent("screen_completed", event.meta, event.elementId, event.reply)
}
is AdaptyOnboardingsAnalyticsEventOnboardingCompleted -> {
// Track successful onboarding completion
trackEvent("onboarding_completed", event.meta)
}
is AdaptyOnboardingsAnalyticsEventUnknown -> {
// Handle unknown events
trackEvent(event.name, event.meta)
}
// Handle other cases as needed
}
}
private fun trackEvent(eventName: String, meta: AdaptyUIOnboardingMeta, elementId: String? = null, reply: String? = null) {
// Implement your analytics tracking here
// For example, send to your analytics service
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplos de eventos (clic para expandir)
```javascript
// OnboardingStarted
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "welcome_screen",
"screenIndex": 0,
"screensTotal": 4
}
}
// ScreenPresented
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "interests_screen",
"screenIndex": 2,
"screensTotal": 4
}
}
// ScreenCompleted
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 1,
"screensTotal": 4
},
"elementId": "profile_form",
"reply": "success"
}
// SecondScreenPresented
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 1,
"screensTotal": 4
}
}
// UserEmailCollected
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 1,
"screensTotal": 4
}
}
// OnboardingCompleted
{
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "final_screen",
"screenIndex": 3,
"screensTotal": 4
}
}
```
---
# File: kmp-onboarding-input
---
---
title: "Procesar datos de onboardings en el SDK de Kotlin Multiplatform"
description: "Guarda y usa datos de onboardings en tu app de Kotlin Multiplatform con el SDK de Adapty."
---
Cuando tus usuarios responden a una pregunta de un cuestionario o introducen datos en un campo de texto, se invocará el método `onboardingViewOnStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código.
Por ejemplo:
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnStateUpdatedAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
elementId: String,
params: AdaptyOnboardingsStateUpdatedParams
) {
// Store user preferences or responses
when (params) {
is AdaptyOnboardingsSelectParams -> {
// Handle single selection
val id = params.id
val value = params.value
val label = params.label
AppLogger.d("Selected option: $label (id: $id, value: $value)")
}
is AdaptyOnboardingsMultiSelectParams -> {
// Handle multiple selections
}
is AdaptyOnboardingsInputParams -> {
// Handle text input
}
is AdaptyOnboardingsDatePickerParams -> {
// Handle date selection
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
Ejemplos de datos guardados (el formato puede variar según tu implementación)
```javascript
// Example of a saved select action
{
"id": "onboarding_on_state_updated_action",
"view": { /* AdaptyUI.OnboardingView object */ },
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "preferences_screen",
"screen_index": 1,
"total_screens": 3
},
"action": {
"element_id": "preference_selector",
"element_type": "select",
"value": {
"id": "option_1",
"value": "premium",
"label": "Premium Plan"
}
}
}
// Example of a saved multi-select action
{
"id": "onboarding_on_state_updated_action",
"view": { /* AdaptyUI.OnboardingView object */ },
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "interests_screen",
"screen_index": 2,
"total_screens": 3
},
"action": {
"element_id": "interests_selector",
"element_type": "multi_select",
"value": [
{
"id": "interest_1",
"value": "sports",
"label": "Sports"
},
{
"id": "interest_2",
"value": "music",
"label": "Music"
}
]
}
}
// Example of a saved input action
{
"id": "onboarding_on_state_updated_action",
"view": { /* AdaptyUI.OnboardingView object */ },
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 0,
"total_screens": 3
},
"action": {
"element_id": "name_input",
"element_type": "input",
"value": {
"type": "text",
"value": "John Doe"
}
}
}
// Example of a saved date picker action
{
"id": "onboarding_on_state_updated_action",
"view": { /* AdaptyUI.OnboardingView object */ },
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 0,
"total_screens": 3
},
"action": {
"element_id": "birthday_picker",
"element_type": "date_picker",
"value": {
"day": 15,
"month": 6,
"year": 1990
}
}
}
```
## Casos de uso \{#use-cases\}
### Enriquecer perfiles de usuario con datos \{#enrich-user-profiles-with-data\}
Si quieres vincular de inmediato los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, debes [actualizar el perfil del usuario](kmp-setting-user-attributes) con los datos del campo al gestionar la acción.
Por ejemplo, si pides a los usuarios que introduzcan su nombre en el campo de texto con el ID `name` y quieres establecer el valor de ese campo como el nombre de pila del usuario, y además les pides que introduzcan su correo electrónico en el campo `email`, el código de tu app podría verse así:
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnStateUpdatedAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
elementId: String,
params: AdaptyOnboardingsStateUpdatedParams
) {
// Store user preferences or responses
when (params) {
is AdaptyOnboardingsInputParams -> {
// Handle text input
val builder = AdaptyProfileParameters.Builder()
// Map elementId to appropriate profile field
when (elementId) {
"name" -> {
when (val input = params.input) {
is AdaptyOnboardingsTextInput -> {
builder.withFirstName(input.value)
}
}
}
"email" -> {
when (val input = params.input) {
is AdaptyOnboardingsEmailInput -> {
builder.withEmail(input.value)
}
}
}
}
// Update profile asynchronously
mainUiScope.launch {
val profileParams = builder.build()
val result = Adapty.updateProfile(profileParams)
result.onSuccess { profile ->
// Profile updated successfully
AppLogger.d("Profile updated: ${profile.email}")
}.onError { error ->
// Handle the error
AppLogger.e("Failed to update profile: ${error.message}")
}
}
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
### Personalizar paywalls según las respuestas \{#customize-paywalls-based-on-answers\}
Usando cuestionarios en los onboardings, también puedes personalizar los paywalls que muestras a los usuarios una vez que completan el onboarding.
Por ejemplo, puedes preguntar a los usuarios sobre su experiencia deportiva y mostrar distintos CTAs y productos a diferentes grupos de usuarios.
1. [Añade un cuestionario](onboarding-quizzes) en el constructor de onboarding y asigna IDs significativos a sus opciones.
2. Gestiona las respuestas del cuestionario según sus IDs y [establece atributos personalizados](kmp-setting-user-attributes) para los usuarios.
```kotlin
class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver {
override fun onboardingViewOnStateUpdatedAction(
view: AdaptyUIOnboardingView,
meta: AdaptyUIOnboardingMeta,
elementId: String,
params: AdaptyOnboardingsStateUpdatedParams
) {
// Handle quiz responses and set custom attributes
when (params) {
is AdaptyOnboardingsSelectParams -> {
// Handle quiz selection
val builder = AdaptyProfileParameters.Builder()
// Map quiz responses to custom attributes
when (elementId) {
"experience" -> {
// Set custom attribute 'experience' with the selected value (beginner, amateur, pro)
builder.withCustomAttribute("experience", params.value)
}
}
// Update profile asynchronously
mainUiScope.launch {
val profileParams = builder.build()
val result = Adapty.updateProfile(profileParams)
result.onSuccess { profile ->
// Profile updated successfully
AppLogger.d("Custom attribute 'experience' set to: ${params.value}")
}.onError { error ->
// Handle the error
AppLogger.e("Failed to update profile: ${error.message}")
}
}
}
}
}
}
// Set up the observer
AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver())
```
3. [Crea segmentos](segments) para cada valor de atributo personalizado.
4. Crea un [placement](placements) y añade [audiencias](audience) para cada segmento que hayas creado.
5. [Muestra un paywall](kmp-paywalls) para el placement en el código de tu app. Si tu onboarding tiene un botón que abre un paywall, implementa el código del paywall como [respuesta a la acción de ese botón](kmp-handling-onboarding-events#opening-a-paywall).
---
# File: kmp-best-practices
---
---
title: "Best practices in Kotlin Multiplatform SDK"
description: "Reference patterns for integrating Adapty SDK on Kotlin Multiplatform — call order, error handling, and other production-readiness rules."
---
---
# File: kmp-sdk-call-order
---
---
title: "Orden de llamadas en el SDK de Kotlin Multiplatform"
description: "Evita perder el acceso premium, datos de atribución incompletos y errores de activación intermitentes llamando a los métodos del SDK de Adapty en el orden correcto."
---
`Adapty.activate()` debe completarse antes de llamar a cualquier otro método del SDK de Adapty. Hasta que finalice, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` fallará con un error de activación. Consulta [Gestionar errores en el SDK de Kotlin Multiplatform](kmp-handle-errors).
Si tu app autentica usuarios y obtienes un customer user ID después del lanzamiento, llama a `Adapty.identify()` en ese momento. No llames a métodos de acción del usuario hasta que `identify` se complete. Las llamadas que compiten con él pueden devolver un error o asociarse al perfil anónimo creado en la activación. Cuando esto ocurre, la atribución, los IDs de MMP como `appsflyer_id` y la propiedad de la instalación no siempre se transfieren al perfil identificado. Si tu app no autentica usuarios, omite `identify` y trabaja con el perfil anónimo.
Los SDKs de MMP y analíticas (AppsFlyer, Adjust, Branch, PostHog) siguen la misma regla. Inicialízalos primero y espera sus callbacks de UID antes de llamar a `Adapty.activate`. De lo contrario, el ID de MMP se asociará a un perfil anónimo temporal y no siempre se transfiere al identificado. Para más detalles sobre AppsFlyer, consulta [AppsFlyer](appsflyer).
## El orden correcto \{#the-correct-order\}
Tu flujo depende de dos factores: cuándo conoces el customer user ID y si usas un SDK de MMP o analíticas.
- **Pasos 2 y 5**: Obligatorios para toda app. Activa el SDK y luego llama a los métodos del SDK.
- **Pasos 1 y 3**: Solo necesarios si integras un SDK de MMP o analíticas (AppsFlyer, Adjust, Branch, PostHog).
- **Paso 4**: Solo necesario si tu app autentica usuarios y obtiene el customer user ID después del lanzamiento.
Si tienes el customer user ID en el lanzamiento de la app, pásalo al `AdaptyConfig.Builder` antes de llamar a `activate()` (paso 2a). Con este flujo nunca se crea un perfil anónimo, por lo que el paso 4 no es necesario.
| Paso | Llamada | Cuándo | Notas |
|------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| 1 | Inicializa tu SDK de MMP o analíticas (AppsFlyer, Adjust, PostHog, Branch) | Lanzamiento de la app, primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. |
| 2a | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Lanzamiento de la app, después del paso 1, si tienes el customer user ID | Recomendado. Nunca se crea un perfil anónimo. |
| 2b | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").build())` sin `withCustomerUserId` | Lanzamiento de la app, después del paso 1, si no tienes el customer user ID (o nunca lo obtienes) | Adapty crea un perfil anónimo. |
| 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción del usuario | Necesario para que los IDs de MMP queden asociados al perfil correcto. |
| 4 | `Adapty.identify("YOUR_USER_ID").onSuccess { ... }.onError { ... }` | Después del paso 3 (o del paso 2 si no hay MMP), antes del paso 5 — solo en el flujo 2b con autenticación | Espera `onSuccess` antes de cualquier llamada de acción del usuario. Las llamadas concurrentes durante `identify` pueden asociarse al perfil anónimo. |
| 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; en caso contrario, después del paso 3 (o del paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. |
:::important
Saltarse estos pasos provoca pérdida de acceso premium para usuarios recurrentes, `appsflyer_id` ausente en los perfiles y paywalls mostrados a la audiencia incorrecta.
:::
## Instalaciones web2app y de embudo web \{#web2app-and-web-funnel-installs\}
Si los usuarios compran en un checkout web (Stripe, Paddle) e instalan la app nativa después, el primer `activate()` del dispositivo crea un nuevo perfil anónimo. Este perfil no está vinculado al perfil web. Si puedes resolver el customer user ID antes del lanzamiento de la app (desde tu flujo de autenticación o el referrer de instalación), pásalo directamente al `AdaptyConfig.Builder`. De lo contrario, la compra web será invisible en el dispositivo hasta que llames a `identify("YOUR_USER_ID")` y luego a `restorePurchases`.
Para los metadatos que debes enviar con cada checkout web, consulta:
- [Stripe](stripe)
- [Paddle](paddle)
---
# File: kmp-optimize-paywall-fetching
---
---
title: "Optimizar la obtención de paywalls en el SDK de Kotlin Multiplatform"
description: "Obtén paywalls de Adapty de forma fiable: tiempos, caché y patrones de respaldo para Kotlin Multiplatform."
---
Una obtención fiable de paywalls en Kotlin Multiplatform hace tres cosas: renderiza rápido, devuelve el paywall dirigido a la audiencia correcta y recurre al respaldo de forma elegante cuando la red es lenta. Las reglas que se presentan a continuación cubren los patrones de tiempos, caché y respaldo para lograrlo.
:::tip
Las reglas asumen que `Adapty.activate()` y `Adapty.identify()` ya se han resuelto. Consulta [Orden de llamadas en el SDK de Kotlin Multiplatform](kmp-sdk-call-order).
:::
## Reglas y errores comunes \{#rules-and-pitfalls\}
| Haz esto | No hagas esto | Por qué |
|---|---|---|
| Obtén el placement que estás a punto de mostrar. | No precargues todos los placements de forma simultánea al arrancar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante el pico de carga. |
| Llama a `getPaywall` después de que la atribución haya tenido oportunidad de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se dispare `setOnProfileUpdatedListener`. | No llames a `getPaywall` al arrancar la app. | La atribución aún no ha llegado. El paywall se resuelve contra la audiencia predeterminada y omite silenciosamente los segmentos y la personalización de ASA. |
| Establece un `loadTimeout` y configura un [paywall de respaldo](fallback-paywalls) para cada placement. | No esperes `getPaywall` indefinidamente. | Sin timeout, los usuarios con mala conectividad ven una pantalla en blanco hasta que la red se resuelva, o cierran la app. |
Consulta [Obtener paywalls y productos](fetch-paywalls-and-products-kmp) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado.
## Ajustar para conectividad deficiente \{#tune-for-poor-connectivity\}
Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte público, regiones afectadas por enrutamiento):
- Establece `fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` en cada obtención excepto la primera.
- Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard.
- Establece `loadTimeout` entre 3 y 5 segundos y acepta el respaldo cuando se agote el tiempo.
- No condicionales la visualización del paywall a `Adapty.getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz.
---
# File: kmp-test
---
---
title: "Prueba y lanzamiento en Kotlin Multiplatform SDK"
description: "Aprende a comprobar el estado de las suscripciones en tu app de Kotlin Multiplatform con Adapty."
---
Si ya has implementado el SDK de Adapty en tu app de Kotlin Multiplatform, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox.
## Prueba tu app \{#test-your-app\}
Para realizar pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas específicas por plataforma: [guía de pruebas de iOS](test-purchases-in-sandbox) y [guía de pruebas de Android](testing-on-android).
## Prepárate para el lanzamiento \{#prepare-for-release\}
Antes de enviar tu app al store, sigue el [checklist de lanzamiento](release-checklist) para confirmar que:
- La conexión con el store y las notificaciones del servidor están configuradas
- Las compras se completan y se notifican a Adapty
- El acceso se desbloquea y se restaura correctamente
- Se cumplen los requisitos de privacidad y revisión
---
# File: kmp-reference
---
---
title: "Reference for Kotlin Multiplatform SDK"
description: "Reference documentation for Adapty Kotlin Multiplatform SDK."
---
This page contains reference documentation for Adapty Kotlin Multiplatform SDK. Choose the topic you need:
- **[SDK models](https://kmp.adapty.io/adapty/)** - Data models and structures used by the SDK
- **[Handle errors](kmp-handle-errors)** - Error handling and troubleshooting
---
# File: kmp-handle-errors
---
---
title: "Handle errors in Kotlin Multiplatform SDK"
description: "Learn how to handle errors in your Kotlin Multiplatform app with Adapty."
---
This page covers error handling in the Adapty Kotlin Multiplatform SDK.
## Error handling basics
All Adapty SDK methods return results that can be either success or error. Always handle both cases:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// Handle success
}
is AdaptyResult.Error -> {
val error = result.error
// Handle error
Log.e("Adapty", "Error: ${error.message}")
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// Handle success
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle error
Log.e("Adapty", "Error: " + error.getMessage());
}
});
```
## Common error codes
| Error Code | Description | Solution |
|------------|-------------|----------|
| 1000 | No product IDs found | Check product configuration in dashboard |
| 1001 | Network error | Check internet connection |
| 1002 | Invalid SDK key | Verify your SDK key |
| 1003 | Can't make payments | Device doesn't support payments |
| 1004 | Product not available | Product not configured in store |
## Handle specific errors
### Network errors
```kotlin showLineNumbers
Adapty.getPaywall("main") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// Use paywall
}
is AdaptyResult.Error -> {
val error = result.error
when (error.code) {
1001 -> {
// Network error - show offline message
showOfflineMessage()
}
else -> {
// Other errors
showErrorMessage(error.message)
}
}
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("main", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// Use paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
switch (error.getCode()) {
case 1001:
// Network error - show offline message
showOfflineMessage();
break;
default:
// Other errors
showErrorMessage(error.getMessage());
break;
}
}
});
```
### Purchase errors
```kotlin showLineNumbers
product.makePurchase { result ->
when (result) {
is AdaptyResult.Success -> {
val purchase = result.value
// Purchase successful
showSuccessMessage()
}
is AdaptyResult.Error -> {
val error = result.error
when (error.code) {
1003 -> {
// Can't make payments
showPaymentNotAvailableMessage()
}
1004 -> {
// Product not available
showProductNotAvailableMessage()
}
else -> {
// Other purchase errors
showPurchaseErrorMessage(error.message)
}
}
}
}
}
```
```java showLineNumbers
product.makePurchase(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchase purchase = ((AdaptyResult.Success) result).getValue();
// Purchase successful
showSuccessMessage();
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
switch (error.getCode()) {
case 1003:
// Can't make payments
showPaymentNotAvailableMessage();
break;
case 1004:
// Product not available
showProductNotAvailableMessage();
break;
default:
// Other purchase errors
showPurchaseErrorMessage(error.getMessage());
break;
}
}
});
```
## Error recovery strategies
### Retry on network errors
```kotlin showLineNumbers
fun getPaywallWithRetry(placementId: String, maxRetries: Int = 3) {
var retryCount = 0
fun attemptGetPaywall() {
Adapty.getPaywall(placementId) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// Use paywall
}
is AdaptyResult.Error -> {
val error = result.error
if (error.code == 1001 && retryCount < maxRetries) {
// Network error - retry
retryCount++
Handler(Looper.getMainLooper()).postDelayed({
attemptGetPaywall()
}, 1000 * retryCount) // Exponential backoff
} else {
// Max retries reached or other error
showErrorMessage(error.message)
}
}
}
}
}
attemptGetPaywall()
}
```
```java showLineNumbers
public void getPaywallWithRetry(String placementId, int maxRetries) {
AtomicInteger retryCount = new AtomicInteger(0);
Runnable attemptGetPaywall = new Runnable() {
@Override
public void run() {
Adapty.getPaywall(placementId, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// Use paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
if (error.getCode() == 1001 && retryCount.get() < maxRetries) {
// Network error - retry
retryCount.incrementAndGet();
new Handler(Looper.getMainLooper()).postDelayed(this, 1000 * retryCount.get());
} else {
// Max retries reached or other error
showErrorMessage(error.getMessage());
}
}
});
}
};
attemptGetPaywall.run();
}
```
### Fallback to cached data
```kotlin showLineNumbers
class PaywallManager {
private var cachedPaywall: AdaptyPaywall? = null
fun getPaywall(placementId: String) {
Adapty.getPaywall(placementId) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
cachedPaywall = paywall
showPaywall(paywall)
}
is AdaptyResult.Error -> {
val error = result.error
if (error.code == 1001 && cachedPaywall != null) {
// Network error - use cached paywall
showPaywall(cachedPaywall!!)
showOfflineIndicator()
} else {
// No cache available or other error
showErrorMessage(error.message)
}
}
}
}
}
}
```
```java showLineNumbers
public class PaywallManager {
private AdaptyPaywall cachedPaywall;
public void getPaywall(String placementId) {
Adapty.getPaywall(placementId, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
cachedPaywall = paywall;
showPaywall(paywall);
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
if (error.getCode() == 1001 && cachedPaywall != null) {
// Network error - use cached paywall
showPaywall(cachedPaywall);
showOfflineIndicator();
} else {
// No cache available or other error
showErrorMessage(error.getMessage());
}
}
});
}
}
```
## Next steps
- [Fix for Code-1000 noProductIDsFound error](InvalidProductIdentifiers-kmp)
- [Fix for Code-1003 cantMakePayments error](cantMakePayments-kmp)
- [Complete API reference](https://android.adapty.io) - Full SDK documentation
---
# File: InvalidProductIdentifiers-kmp
---
---
title: "Solución al error Code-1000 noProductIDsFound en el SDK de Kotlin Multiplatform"
description: "Resuelve errores de identificador de producto no válido al gestionar suscripciones en Adapty."
---
El error con código 1000, `noProductIDsFound`, indica que ninguno de los productos que solicitaste en el paywall está disponible para comprar en el App Store, aunque estén listados allí. A veces este error viene acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin el error, puedes ignorarla sin problema.
Si te encuentras con el error `noProductIDsFound`, sigue estos pasos para resolverlo:
## Paso 1. Comprueba el bundle ID \{#step-2-check-bundle-id\}
---
no_index: true
---
1. Abre [App Store Connect](https://appstoreconnect.apple.com/apps). Selecciona tu app y ve a la sección **General** → **App Information**.
2. Copia el **Bundle ID** en la subsección **General Information**.
3. Abre la pestaña [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) desde el menú superior de Adapty y pega el valor copiado en el campo **Bundle ID**.
4. Vuelve a la página **App information** en App Store Connect y copia el **Apple ID** desde allí.
5. En la página [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) del Adapty Dashboard, pega el ID en el campo **Apple app ID**.
## Paso 2. Comprueba los productos \{#step-3-check-products\}
1. Ve a **App Store Connect** y navega a [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) en el menú de la izquierda.
2. Haz clic en el nombre del grupo de suscripción. Verás tus productos listados en la sección **Subscriptions**.
3. Asegúrate de que el producto que estás probando esté marcado como **Ready to Submit**. Si no lo está, sigue las instrucciones de la página [Producto en App Store](app-store-products).
4. Compara el ID del producto en la tabla con el que aparece en la pestaña [**Products**](https://app.adapty.io/products) del Adapty Dashboard. Si los IDs no coinciden, copia el ID del producto de la tabla y [crea un producto](create-product) con ese ID en el Adapty Dashboard.
## Paso 3. Comprueba la disponibilidad del producto \{#step-4-check-product-availability\}
1. Vuelve a **App Store Connect** y abre la misma sección **Subscriptions**.
2. Haz clic en el nombre del grupo de suscripción para ver tus productos.
3. Selecciona el producto que estás probando.
4. Desplázate hasta la sección **Availability** y comprueba que todos los países y regiones requeridos estén listados.
## Paso 4. Comprueba los precios del producto \{#step-5-check-product-prices\}
1. De nuevo, ve a la sección **Monetization** → **Subscriptions** en **App Store Connect**.
2. Haz clic en el nombre del grupo de suscripción.
3. Selecciona el producto que estás probando.
4. Desplázate hacia abajo hasta **Subscription Pricing** y despliega la sección **Current Pricing for New Subscribers**.
5. Asegúrate de que todos los precios requeridos estén listados.
## Paso 5. Comprueba que el estado de pago de la app, la cuenta bancaria y los formularios fiscales estén activos \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\}
1. En la página de inicio de [**App Store Connect**](https://appstoreconnect.apple.com/), haz clic en **Business**.
2. Selecciona el nombre de tu empresa.
3. Desplázate hacia abajo y comprueba que tu **Paid Apps Agreement**, **Bank Account** y **Tax forms** aparezcan como **Active**.
Siguiendo estos pasos deberías poder resolver la advertencia `InvalidProductIdentifiers` y hacer que tus productos estén disponibles en el store.
## Paso 6. Vuelve a crear el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\}
Es posible que los pasos 1–5 pasen correctamente —estado `Approved`, Bundle ID coincidente, API key válida— y el SDK siga devolviendo `1000 noProductIDsFound`. En ese caso, puede que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple puede entrar en un estado en el que el producto existe en la interfaz de App Store Connect pero no está expuesto a la ruta de búsqueda de StoreKit.
Elimina el producto en App Store Connect y vuelve a crearlo con el mismo ID de producto. Espera hasta 24 horas tras la recreación para que los cambios se propaguen.
---
# File: cantMakePayments-kmp
---
---
title: "Solución para el error Code-1003 cantMakePayment en el SDK de Kotlin Multiplatform"
description: "Resuelve el error al realizar pagos cuando gestionas suscripciones en Adapty."
---
El error 1003, `cantMakePayments`, indica que no es posible realizar compras in-app en este dispositivo.
Si encuentras el error `cantMakePayments`, normalmente se debe a una de estas razones:
- Restricciones del dispositivo: El error no está relacionado con Adapty. Consulta las soluciones más abajo.
- Configuración del modo Observer: El método `makePurchase` y el modo Observer no pueden usarse al mismo tiempo. Consulta la sección más abajo.
## Problema: Restricciones del dispositivo \{#issue-device-restrictions\}
| Problema | Solución |
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| Restricciones de Screen Time | Desactiva las restricciones de compras in-app en [Screen Time](https://support.apple.com/en-us/102470) |
| Cuenta suspendida | Contacta con el soporte de Apple para resolver problemas con la cuenta |
| Restricciones regionales | Usa una cuenta de App Store de una región compatible |
## Problema: Usar el modo Observer y makePurchase a la vez \{#issue-using-both-observer-mode-and-makepurchase\}
Si usas `makePurchases` para gestionar las compras, no necesitas el modo Observer. El [modo Observer](observer-vs-full-mode) solo es necesario si implementas la lógica de compra tú mismo.
Por lo tanto, si usas `makePurchase`, puedes eliminar sin problema la activación del modo Observer del código de inicialización del SDK.
---
# File: kmp-sdk-migration-guides
---
---
title: "Kotlin Multiplatform SDK Migration Guides"
description: "Migration guides for Adapty Kotlin Multiplatform SDK versions."
---
This page contains all migration guides for Adapty Kotlin Multiplatform SDK. Choose the version you want to migrate to for detailed instructions:
- **[Migrate to v. 3.15](migration-to-kmp-315)**
---
# File: migration-to-kmp-315
---
---
title: "Guía de migración al SDK de Adapty Kotlin Multiplatform 3.15.0"
description: "Pasos de migración para el SDK de Adapty Kotlin Multiplatform 3.15.0"
---
El SDK de Adapty Kotlin Multiplatform 3.15.0 es una versión mayor que trae nuevas funcionalidades y mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte.
1. Actualiza los nombres de la clase observer y sus métodos.
2. Actualiza el nombre del método para los paywalls de respaldo.
3. Actualiza el nombre de la clase de vista en los métodos de gestión de eventos.
## Actualiza los nombres de la clase observer y sus métodos \{#update-observer-class-and-method-names\}
La clase observer y su método de registro han sido renombrados:
```diff
- import com.adapty.kmp.AdaptyUIObserver
+ import com.adapty.kmp.AdaptyUIPaywallsEventsObserver
- import com.adapty.kmp.models.AdaptyUIView
+ import com.adapty.kmp.models.AdaptyUIPaywallView
- class MyAdaptyUIObserver : AdaptyUIObserver {
- override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) {
+ class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver {
+ override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) {
// handle actions
}
}
// Set up the observer
- AdaptyUI.setObserver(MyAdaptyUIObserver())
+ AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver())
```
## Actualiza el nombre del método para los paywalls de respaldo \{#update-fallback-paywalls-method-name\}
El nombre del método para configurar los paywalls de respaldo ha cambiado:
```diff showLineNumbers
- Adapty.setFallbackPaywalls(assetId = "fallback.json")
+ Adapty.setFallback(assetId = "fallback.json")
.onSuccess {
// Fallback paywalls loaded successfully
}
.onError { error ->
// Handle the error
}
```
## Actualiza el nombre de la clase de vista en los métodos de gestión de eventos \{#update-view-class-name-in-event-handling-methods\}
Todos los métodos de gestión de eventos usan ahora la nueva clase `AdaptyUIPaywallView` en lugar de `AdaptyUIView`:
```diff
- override fun paywallViewDidAppear(view: AdaptyUIView) {
+ override fun paywallViewDidAppear(view: AdaptyUIPaywallView) {
// Handle paywall appearance
}
- override fun paywallViewDidDisappear(view: AdaptyUIView) {
+ override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) {
// Handle paywall disappearance
}
- override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
+ override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) {
// Handle product selection
}
- override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) {
+ override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
// Handle purchase start
}
- override fun paywallViewDidFinishPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) {
+ override fun paywallViewDidFinishPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) {
// Handle purchase result
}
- override fun paywallViewDidFailPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, error: AdaptyError) {
+ override fun paywallViewDidFailPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError) {
// Add your purchase failure handling logic here
}
- override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) {
+ override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) {
// Add your successful restore handling logic here
}
- override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your restore failure handling logic here
}
- override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIView, product: AdaptyPaywallProduct?, error: AdaptyError?) {
+ override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError?) {
// Handle web payment navigation result
}
- override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) {
// Add your product loading failure handling logic here
}
- override fun paywallViewDidFailRendering(view: AdaptyUIView, error: AdaptyError) {
+ override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {
// Handle rendering error
}
```
---
# End of Documentation
_Generated on: 2026-05-22T06:52:52.323Z_
_Successfully processed: 45/45 files_