# FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: es Generated on: 2026-05-22T06:52:52.247Z Total files: 40 --- # File: sdk-installation-flutter --- --- title: "Instalar y configurar el SDK de Flutter" description: "Guía paso a paso para instalar el SDK de Adapty en Flutter en apps con suscripciones." --- El SDK de Adapty incluye dos módulos clave para una integración fluida en tu app de Flutter: - **Core Adapty**: el SDK esencial, necesario para que Adapty funcione correctamente en tu app. - **AdaptyUI**: necesario si usas el [Adapty Paywall Builder](adapty-paywall-builder), una herramienta visual sin código para crear paywalls multiplataforma fácilmente. :::tip ¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Consulta nuestra [app de ejemplo](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example), que muestra la configuración completa: visualización de paywalls, compras y otras funciones básicas. ::: ## Requisitos \{#requirements\} El SDK de Adapty es compatible con iOS 13.0+, pero requiere iOS 15.0+ para funcionar correctamente con paywalls creados en el Paywall Builder. :::info Adapty es compatible con Google Play Billing Library hasta la versión 8.x. Por defecto, Adapty trabaja con Google Play Billing Library v.7.0.0, pero si quieres forzar una versión posterior, puedes [añadir la dependencia](https://developer.android.com/google/play/billing/integrate#dependency) manualmente. ::: --- no_index: true --- import Callout from '../../../components/Callout.astro'; Instalar el SDK es el paso 5 de la configuración de Adapty. Para que las compras funcionen en tu app, también necesitas conectar tu app a los stores, y luego crear productos, un paywall y un placement en el Adapty Dashboard. La [guía de inicio rápido](quickstart) explica todos los pasos necesarios. ## Instalar el SDK de Adapty \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) 1. Añade Adapty a tu archivo `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^ ``` 2. Ejecuta el siguiente comando para instalar las dependencias: ```bash showLineNumbers title="Terminal" flutter pub get ``` 3. Importa los SDKs de Adapty en tu aplicación: ```dart showLineNumbers title="main.dart" import 'package:adapty_flutter/adapty_flutter.dart'; ``` ## Activar el módulo Adapty del SDK de Adapty \{#activate-adapty-module-of-adapty-sdk\} Activa el SDK de Adapty en el código de tu app. :::note El SDK de Adapty solo necesita activarse una vez en tu app. ::: Para obtener tu **Public SDK Key**: 1. Ve al Adapty Dashboard y navega a [**App settings → General**](https://app.adapty.io/settings/general). 2. En la sección **Api keys**, copia la **Public SDK Key** (NO la Secret Key). 3. Reemplaza `"YOUR_PUBLIC_SDK_KEY"` en el código. :::important - Asegúrate de usar la **Public SDK key** para inicializar Adapty; la **Secret key** solo debe usarse para la [API del servidor](getting-started-with-server-side-api). - Las **SDK keys** son únicas para cada app, así que si tienes varias apps asegúrate de elegir la correcta. ::: ```dart showLineNumbers title="main.dart" void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { _initializeAdapty(); super.initState(); } Future _initializeAdapty() async { try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), ); } catch (e) { // handle the error } } Widget build(BuildContext context) { return Text("Hello"); } } ``` :::important Espera a que `activate` se resuelva antes de llamar a cualquier otro método del SDK de Adapty. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order) para ver la secuencia completa. ::: Ahora configura los paywalls en tu app: - Si usas [Adapty Paywall Builder](adapty-paywall-builder), primero [activa el módulo AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) a continuación y luego sigue la [guía de inicio rápido de Paywall Builder](flutter-quickstart-paywalls). - Si construyes tu propia UI de paywall, consulta la [guía de inicio rápido para paywalls personalizados](flutter-quickstart-manual). ## Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Si tienes previsto usar el [Paywall Builder](adapty-paywall-builder) y has [instalado el módulo AdaptyUI](sdk-installation-flutter#install-adapty-sdk), también necesitas activar AdaptyUI: :::note Las dependencias relacionadas con AdaptyUI se vinculan a tu app independientemente de si AdaptyUI está activado. ::: :::important En tu código, debes activar el módulo principal de Adapty antes de activar AdaptyUI. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withActivateUI(true), // This automatically activates AdaptyUI ); ``` ## Configuración opcional \{#optional-setup\} ### Registro de actividad \{#logging\} #### Configurar el sistema de registro \{#set-up-the-logging-system\} Adapty registra errores y otra información importante para ayudarte a entender qué está ocurriendo. Los niveles disponibles son los siguientes: | Nivel | Descripción | | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | `AdaptyLogLevel.none` | No se registrará nada. Valor por defecto | | `AdaptyLogLevel.error` | Solo se registrarán los errores | | `AdaptyLogLevel.warn` | Se registrarán los errores y los mensajes del SDK que no causan errores críticos pero merecen atención. | | `AdaptyLogLevel.info` | Se registrarán errores, advertencias y varios mensajes informativos. | | `AdaptyLogLevel.verbose` | Se registrará cualquier información adicional que pueda resultar útil durante la depuración, como llamadas a funciones, consultas a la API, etc. | Puedes establecer el nivel de registro en tu app antes de configurar Adapty: ```dart showLineNumbers title="main.dart" // Set log level before activation. // 'verbose' is recommended for development and the first production release await Adapty().setLogLevel(AdaptyLogLevel.verbose); // Or set it during configuration await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withLogLevel(AdaptyLogLevel.verbose), ); ``` ### Políticas de datos \{#data-policies\} Adapty no almacena datos personales de tus usuarios a menos que los envíes explícitamente, pero puedes implementar políticas de seguridad de datos adicionales para cumplir con las directrices del store o del país. #### Desactivar la recopilación y el uso compartido de direcciones IP \{#disable-ip-address-collection-and-sharing\} Al activar el módulo de Adapty, establece `ipAddressCollectionDisabled` en `true` para desactivar la recopilación y el uso compartido de la dirección IP del usuario. El valor por defecto es `false`. Usa este parámetro para mejorar la privacidad del usuario, cumplir con las normativas regionales de protección de datos (como el RGPD o la CCPA), o reducir la recopilación innecesaria de datos cuando las funciones basadas en IP no son necesarias para tu app. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withIpAddressCollectionDisabled(true), ); ``` #### Desactivar la recopilación y el uso compartido del ID publicitario \{#disable-advertising-id-collection-and-sharing\} Al activar el módulo de Adapty, establece `appleIdfaCollectionDisabled` (iOS) o `googleAdvertisingIdCollectionDisabled` (Android) en `true` para desactivar la recopilación de identificadores publicitarios. El valor por defecto es `false`. Usa este parámetro para cumplir con las políticas de App Store/Play Store, evitar que se active la solicitud de App Tracking Transparency, o si tu app no requiere atribución publicitaria ni analíticas basadas en IDs publicitarios. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleIdfaCollectionDisabled(true) // iOS ..withGoogleAdvertisingIdCollectionDisabled(true), // Android ); ``` #### Configurar la caché de medios para AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} El módulo se activa automáticamente con el SDK de Adapty. Si no usas el Paywall Builder y quieres desactivar el módulo AdaptyUI, pasa `withActivateUI(false)` durante la activación. Por defecto, AdaptyUI almacena en caché los medios (como imágenes y vídeos) para mejorar el rendimiento y reducir el uso de red. Puedes personalizar la configuración de la caché proporcionando una configuración personalizada. Usa `withMediaCacheConfiguration` para cambiar el tamaño predeterminado de la caché y el período de validez. Es opcional: si no llamas a este método, se usarán los valores por defecto (100 MB en disco, sin límite de elementos en memoria). Sin embargo, si usas la configuración, debes incluir todos los parámetros. ```dart showLineNumbers title="main.dart" final mediaCacheConfig = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit: 2147483647, // max int value diskStorageSizeLimit: 200 * 1024 * 1024, // 200 MB ); await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withMediaCacheConfiguration(mediaCacheConfig), ); ``` **Parámetros:** | Parámetro | Presencia | Descripción | |-----------------------------|-----------|-----------------------------------------------------------------------------------------| | memoryStorageTotalCostLimit | opcional | Tamaño total de la caché en memoria en bytes. El valor por defecto es 100 MB. | | memoryStorageCountLimit | opcional | Límite de elementos en el almacenamiento en memoria. El valor por defecto es el máximo entero. | | diskStorageSizeLimit | opcional | Límite del tamaño de archivo en disco en bytes. El valor por defecto es 100 MB. | ### Activar niveles de acceso locales (Android) \{#enable-local-access-levels-android\} Por defecto, los [niveles de acceso locales](local-access-levels) están habilitados en iOS y deshabilitados en Android. Para habilitarlos también en Android, establece `withGoogleLocalAccessLevelAllowed` en `true`: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleLocalAccessLevelAllowed(true), ); ``` ### Borrar datos al restaurar desde copia de seguridad \{#clear-data-on-backup-restore\} Cuando `clearDataOnBackup` se establece en `true`, el SDK detecta cuándo la app se restaura desde una copia de seguridad de iCloud y elimina todos los datos del SDK almacenados localmente, incluyendo la información de perfil en caché, los detalles de productos y los paywalls. El SDK se inicializa entonces con un estado limpio. El valor por defecto es `false`. :::note Solo se elimina la caché local del SDK. El historial de transacciones con Apple y los datos del usuario en los servidores de Adapty permanecen sin cambios. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withClearDataOnBackup(true) // default – false ); ``` ## Solución de problemas \{#troubleshooting\} #### Reglas de copia de seguridad en Android (configuración de Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Algunos SDKs (incluido Adapty) incluyen su propia configuración de Android Auto Backup. Si utilizas varios SDKs que definen reglas de copia de seguridad, el fusionador de manifiestos de Android puede fallar con un error relacionado con `android:fullBackupContent`, `android:dataExtractionRules` o `android:allowBackup`. Síntomas típicos del error: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Estos cambios deben realizarse en el directorio de la plataforma Android (normalmente en la carpeta `android/` de tu proyecto). ::: Para resolverlo, necesitas: - Indicar al fusionador de manifiestos que use los valores de tu app para los atributos relacionados con la copia de seguridad. - Crear archivos de reglas de copia de seguridad que combinen las reglas de Adapty con las de otros SDKs. #### 1. Añade el namespace `tools` a tu manifiesto \{#1-add-the-tools-namespace-to-your-manifest\} En tu archivo `AndroidManifest.xml`, asegúrate de que la etiqueta raíz `` incluya tools: ```xml ... ``` #### 2. Sobreescribe los atributos de copia de seguridad en `` \{#2-override-backup-attributes-in-application\} En el mismo archivo `AndroidManifest.xml`, actualiza la etiqueta `` para que tu app proporcione los valores definitivos e indique al fusionador de manifiestos que reemplace los valores de las librerías: ```xml ... ``` Si algún SDK también define `android:allowBackup`, inclúyelo en `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Crea los archivos de reglas de copia de seguridad combinadas \{#3-create-merged-backup-rules-files\} Crea archivos XML en el directorio `res/xml/` de tu proyecto Android que combinen las reglas de Adapty con las de otros SDKs. Android utiliza distintos formatos de reglas de copia de seguridad según la versión del sistema operativo, por lo que crear ambos archivos garantiza la compatibilidad con todas las versiones de Android que admite tu app. :::note Los ejemplos a continuación usan AppsFlyer como SDK de terceros de muestra. Reemplaza o añade reglas para cualquier otro SDK que uses en tu app. ::: **Para Android 12 y superior** (usa el nuevo formato de reglas de extracción de datos): ```xml title="sample_data_extraction_rules.xml" ``` **Para Android 11 e inferior** (usa el formato legado de contenido de copia de seguridad completa): ```xml title="sample_backup_rules.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 utiliza un `launchMode` distinto al predeterminado, Android puede recrearla o reutilizarla de forma incorrecta cuando el usuario vuelve desde Google Play, una app bancaria o un navegador. Esto puede provocar que el resultado de la compra se pierda o se trate como cancelado. Para garantizar que las compras funcionen correctamente, usa solo los modos de lanzamiento `standard` o `singleTop` para 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 tenga el modo `standard` o `singleTop`: ```xml ``` #### Errores de compilación de Swift 6 causados por la sobreescritura de SWIFT_VERSION en el Podfile \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} Al compilar tu app de Flutter para iOS, es posible que veas errores de compilación de Swift 6 en los targets de los pods de Adapty. Los síntomas habituales incluyen incompatibilidades de `@Sendable` en `AdaptyUIBuilderLogic`, falta de conformidad `Sendable` en los tipos de Adapty, o errores de aislamiento de actores. Los pods de Adapty declaran `s.swift_version = '6.0'` y requieren Swift 6 para compilar. El código de tu propia app puede seguir usando Swift 5: solo los targets de los pods de Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) necesitan compilarse con Swift 6. La causa más común es un hook `post_install` en `ios/Podfile` que sobreescribe `SWIFT_VERSION` para todos los targets de pods: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Solución**: excluye los targets de los pods de Adapty de la sobreescritura: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Luego ejecuta `pod install` desde el directorio `ios/` y vuelve a compilar. Para verificarlo, abre `ios/Pods/Pods.xcodeproj`, selecciona el target del pod `Adapty` → **Build Settings** → **Swift Language Version**. Debe aparecer como **Swift 6**. --- # File: flutter-quickstart-paywalls --- --- title: "Habilitar compras usando paywalls en Flutter SDK" 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 pueden 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 diferentes 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 app: | 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 complejo, la validación de recibos y la administración de suscripciones en segundo plano. | | Paywalls creados manualmente | 🟡 Medio | 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](flutter-quickstart-manual). | | Modo observador | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo observador tiene sus limitaciones en Adapty. Consulta el [artículo](observer-vs-full-mode). | :::important **Los pasos a continuación muestran 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](flutter-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. Por ejemplo, abrir enlaces o cerrar el paywall cuando los usuarios pulsen botones. ## Antes de comenzar \{#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](create-paywall). 4. [Crea un placement y añade tu paywall](create-placement). 5. [Instala y activa el SDK de Adapty](sdk-installation-flutter) 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 el [CLI para desarrolladores](developer-cli-quickstart). ::: ## 1. Obtener el paywall \{#1-get-the-paywall\} Tus paywalls están asociados a los placements configurados en el dashboard. Los placements te permiten mostrar diferentes paywalls para distintas audiencias o ejecutar [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 verificar si es un paywall creado en el builder usando la propiedad `hasViewConfiguration`. 2. Crear la vista del paywall usando el método `createPaywallView`. La vista contiene los elementos de UI y el estilo necesarios para mostrar el paywall. :::important Para obtener la configuración de la 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á. ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // 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, usa el método `view.present()` sobre el `view` creado por el método `createPaywallView`. 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`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Para más detalles sobre cómo mostrar un paywall, consulta nuestra [guía](flutter-present-paywalls). ::: ## 3. Gestionar las acciones de los botones \{#3-handle-button-actions\} Cuando los usuarios pulsan botones en el paywall, el SDK de Flutter gestiona automáticamente las compras y la restauración. Sin embargo, otros botones tienen IDs personalizados o predefinidos y requieren gestionar las acciones en tu código. Para controlar o monitorizar los procesos en la pantalla del paywall, implementa los métodos de `AdaptyUIPaywallsEventsObserver` y establece el observer antes de presentar cualquier pantalla. Si un usuario ha realizado alguna acción, se invocará `paywallViewDidPerformAction` y tu app deberá responder según el ID de la acción. Por ejemplo, tu paywall probablemente tenga un botón de cerrar y URLs que abrir (como los términos de uso y la política de privacidad). Por lo tanto, debes responder a las acciones con los IDs `Close` y `OpenUrl`. :::tip Lee nuestras guías sobre cómo gestionar [acciones](flutter-handle-paywall-actions) y [eventos](flutter-handling-events) de botones. ::: ```dart showLineNumbers title="Flutter" class _PaywallScreenState extends State implements AdaptyUIPaywallsEventsObserver { @override void initState() { super.initState(); // Register this class as the paywalls event observer AdaptyUI().setPaywallsEventsObserver(this); } // This method is called when user performs an action on the paywall UI @override void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL using url_launcher package _launchUrl(url); break; } } // Helper method to launch URLs Future _launchUrl(String url) async { try { final Uri uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { // Handle case where URL cannot be launched print('Could not launch $url'); } } catch (e) { // Handle any errors print('Error launching URL: $e'); } } } ``` ## Próximos pasos \{#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. Ahora necesitas [comprobar el nivel de acceso de los usuarios](flutter-check-subscription-status) para asegurarte de que muestras un paywall o concedes acceso a las funciones de pago a los usuarios correctos. ## Ejemplo completo \{#full-example\} Así es como se pueden integrar todos estos pasos en tu app. ```dart void main() async { runApp(MaterialApp(home: PaywallScreen())); } class PaywallScreen extends StatefulWidget { @override State createState() => _PaywallScreenState(); } class _PaywallScreenState extends State implements AdaptyUIPaywallsEventsObserver { @override void initState() { super.initState(); // Register this class as the paywalls event observer AdaptyUI().setPaywallsEventsObserver(this); _showPaywallIfNeeded(); } Future _showPaywallIfNeeded() async { try { final paywall = await Adapty().getPaywall( placementId: 'YOUR_PLACEMENT_ID', ); if (!paywall.hasViewConfiguration) return; final view = await AdaptyUI().createPaywallView(paywall: paywall); await view.present(); } catch (_) { // Handle any errors (network, SDK issues, etc.) } } // This method is called when user performs an action on the paywall UI @override void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL using url_launcher package _launchUrl(url); break; } } // Helper method to launch URLs Future _launchUrl(String url) async { try { final Uri uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { // Handle case where URL cannot be launched print('Could not launch $url'); } } catch (e) { // Handle any errors print('Error launching URL: $e'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Adapty Paywall Example')), body: Center( // Add a button to re-trigger the paywall for testing purposes child: ElevatedButton( onPressed: _showPaywallIfNeeded, child: Text('Show Paywall'), ), ), ); } } ``` --- # File: flutter-check-subscription-status --- --- title: "Verificar el estado de la suscripción en el SDK de Flutter" description: "Aprende cómo verificar el estado de la suscripción en tu app de Flutter con Adapty." --- Para decidir si los usuarios pueden acceder al contenido de pago o ver un paywall, necesitas comprobar su [nivel de acceso](access-level) en el perfil. Este artículo te 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 (por ejemplo, al lanzar la app) o quieres forzar una actualización. - Configura **actualizaciones automáticas del perfil** para mantener una copia local que se actualice automáticamente cada vez que cambie 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: ```javascript showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Escuchar actualizaciones de la suscripción \{#listen-to-subscription-updates\} Para recibir actualizaciones del perfil automáticamente en tu app: 1. Usa `Adapty().didUpdateProfileStream.listen()` para escuchar los cambios del perfil: Adapty llamará a este método automáticamente cada vez que cambie el estado de la suscripción del usuario. 2. Guarda los datos del perfil actualizado cuando se invoque este método, para poder usarlos en toda tu app sin realizar peticiones de red adicionales. ```dart class SubscriptionManager { AdaptyProfile? _currentProfile; SubscriptionManager() { // Listen for profile updates Adapty().didUpdateProfileStream.listen((profile) { _currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() bool hasAccess() { return _currentProfile?.accessLevels['premium']?.isActive ?? false; } } ``` :::note Adapty llama automáticamente al listener del stream 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 situaciones como el lanzamiento de la app, al acceder a secciones premium o antes de mostrar contenido específico. ```dart Future _checkAccessLevel() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false; } catch (e) { print('Error checking access level: $e'); return false; // Show paywall if access check fails } } Future _initializePaywall() async { await _loadPaywall(); final hasAccess = await _checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } ``` ## 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](flutter-quickstart-identify) para asegurarte de que pueden acceder a lo que han pagado. --- # File: flutter-quickstart-identify --- --- title: "Identificar usuarios en el SDK de Flutter" description: "Guía de inicio rápido para configurar Adapty para la gestión de suscripciones in-app en Flutter." --- :::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 alinee 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 y no 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 funcione el SDK. Adapty los crea automáticamente. - Pueden ser anónimos **(sin customer user ID)** o identificados **(con customer user ID)**. - Tú proporcionas el **customer user ID** para relacionar los perfiles de Adapty con tu sistema de autenticación interno. Esto es lo que diferencia a los usuarios anónimos de los identificados: | | Usuarios anónimos | Usuarios identificados | |------------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------| | **Gestión de compras** | Restauración de compras a nivel de store | Mantienen el historial de compras en todos los dispositivos mediante su customer user ID | | **Gestión de perfiles** | Nuevo perfil en cada reinstalación | El mismo perfil en todas las sesiones y dispositivos | | **Persistencia de datos** | Los datos de usuarios anónimos están vinculados a la instalación | 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 se activa el SDK en el primer lanzamiento de la app, Adapty **crea un nuevo perfil para el usuario**. 2. Cuando el usuario realiza una compra en la app, esta compra se **asocia a su perfil de Adapty y a su cuenta del store**. 3. Cuando el usuario **reinstala** la app o la instala en un **dispositivo nuevo**, Adapty **crea un nuevo perfil anónimo al activarse**. 4. Si el usuario había realizado compras anteriormente en tu app, de forma predeterminada sus compras se sincronizan automáticamente desde el App Store al activarse el SDK. Con usuarios anónimos se crearán nuevos perfiles en cada instalación, pero 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 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. ## 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 se inicia tu app, 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 se inicia la app, envíalo al llamar a `activate()`. :::important De forma predeterminada, cuando Adapty recibe una compra de un Customer User ID que está actualmente asociado a otro Customer User ID, el nivel de acceso se comparte, de modo que ambos perfiles tienen acceso de pago. Puedes configurar este ajuste para transferir el acceso de pago de un perfil a otro o desactivar el uso compartido por completo. 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 del lanzamiento de la app (por ejemplo, después de que inicien sesión o se registren), 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**, Adapty cambiará al perfil asociado a ese customer user ID. :::important Los customer user IDs deben ser únicos para cada usuario. Si defines el valor del parámetro de forma fija en el código, todos los usuarios se considerarán como uno solo. ::: Siempre usa `await` con `identify` antes de llamar a otros métodos del SDK. Las llamadas concurrentes generan `#3006 profileWasChanged` o se aplican al perfil anónimo. Consulta [Orden de llamadas en el SDK de Flutter](flutter-sdk-call-order). ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### 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 el customer user ID pero lo defines solo después de la activación, Adapty creará un nuevo perfil anónimo al activarse y cambiará al existente solo después de que llames a `identify`. Puedes pasar un customer user ID existente (uno que hayas usado antes) o uno nuevo. Si pasas uno nuevo, el perfil creado al activarse se vinculará automáticamente al customer user ID. :::note De forma predeterminada, la creación de perfiles anónimos no afecta a los dashboards de análisis, porque las instalaciones se cuentan según los IDs de dispositivo. Un ID de dispositivo representa una única instalación de la app desde el store en un dispositivo y solo se regenera después de reinstalar la app. No depende de si es una primera o posterior instalación, 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 por usuarios únicos en lugar de por dispositivos, ve a **App settings** y configura [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```dart showLineNumbers" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. ); } catch (e) { // handle the error } ``` ### Cerrar sesión de usuarios \{#log-users-out\} Si tienes un botón para cerrar sesión de los usuarios, usa el método `logout`. :::important Cerrar la sesión de un usuario crea un nuevo perfil anónimo para ese usuario. ::: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown 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 conserven el acceso después de iniciar 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 cambia 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 se mantiene todo el historial de compras. - Si es un customer user ID existente (el customer user ID ya está vinculado a un perfil), debes obtener el nivel de acceso actual después del cambio de perfil. Puedes llamar a [`getProfile`](flutter-check-subscription-status) justo después de la identificación, o [escuchar las actualizaciones del perfil](flutter-check-subscription-status) para que los datos se sincronicen automáticamente. ## Siguientes pasos \{#next-steps\} ¡Enhorabuena! Has implementado la lógica de pagos in-app en tu app. ¡Te deseamos mucho éxito con la monetización de tu app! Para sacar aún más partido a Adapty, puedes explorar estos temas: - [**Pruebas**](troubleshooting-test-purchases): Verifica que todo funciona como se espera - [**Onboardings**](flutter-onboardings): Engancha a los usuarios con onboardings y mejora la retención - [**Integraciones**](configuration): Integra con servicios de atribución de marketing y análisis con una sola línea de código - [**Establecer atributos de perfil personalizados**](flutter-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-flutter --- --- title: "Integra Adapty en tu app de Flutter con asistencia de IA" description: "Una guía paso a paso para integrar Adapty en tu app de Flutter usando Cursor, Context7, ChatGPT, Claude u otras herramientas de IA." --- Esta página explica dos formas de integrar Adapty en tu app de Flutter. Usa la habilidad de integración del SDK que se describe a continuación para un flujo automatizado de extremo a extremo, o sigue el recorrido manual más adelante. ## Usa la habilidad de integración del SDK (beta) \{#use-the-sdk-integration-skill-beta\} La [habilidad adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) automatiza la integración de principio a fin: configuración del dashboard, instalación del SDK, paywall y verificación por etapas. El recorrido manual que aparece más abajo 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 habilidad](https://github.com/adaptyteam/adapty-sdk-integration-skill). - **Claude Code**: Ejecuta `claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill` y después `claude plugin install adapty-sdk-integration@adapty` desde tu shell. - **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 habilidades de tu herramienta. ### Ejecución \{#run\} Desde tu proyecto, ejecuta `/adapty-sdk-integration`. La habilidad 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 en cada etapa. :::note La habilidad está en beta. Si se detiene o se comporta de forma inesperada, el recorrido manual que aparece a continuación 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 del SDK. Puedes hacerlo con una habilidad de LLM interactiva o manualmente desde el Dashboard. ### Enfoque con habilidad (recomendado) \{#skill-approach-recommended\} La habilidad 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) desde el Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Una vez añadida la habilidad, ejecuta `/adapty-cli` en tu agente. Te guiará por cada paso, incluido cuándo abrir el Dashboard para conectar tus stores. ### Enfoque manual desde el dashboard \{#dashboard-approach\} Si prefieres configurarlo todo manualmente, esto es lo que necesitas antes de escribir código. Tu LLM no puede buscar los valores del dashboard por ti, así que tendrás que proporcionarlos. 1. **Conecta tus stores**: En el Adapty Dashboard, ve a **App settings → General**. Conecta App Store y Google Play si tu app de Flutter se dirige a ambas plataformas. Esto es obligatorio para que las compras funcionen. [Conectar stores](integrate-payments) 2. **Copia tu clave SDK pública**: En el Adapty Dashboard, ve a **App settings → General** y localiza la sección **API keys**. En el código, esta es la cadena que pasas a la configuración de Adapty. 3. **Crea al menos un producto**: En el Adapty Dashboard, ve a la página **Products**. No referenciarás 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()`. [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 que se comprueba es `profile.accessLevels['premium']?.isActive`. El nivel de acceso `premium` predeterminado funciona para la mayoría de las apps. Si los usuarios de pago acceden a distintas funciones según el producto (por ejemplo, un plan `basic` frente a uno `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 SDK pública es X, mi ID de placement es Y" para que pueda generar el código correcto de inicialización y obtención del paywall. ::: ### Configura cuando estés listo \{#set-up-when-ready\} Estos pasos no son necesarios para empezar a programar, pero los querrás a medida que tu integración madure: - **Pruebas A/B**: Configúralas en la página **Placements**. No requieren cambios en el código. [Pruebas A/B](ab-tests) - **Paywalls y placements adicionales**: Añade más llamadas a `getPaywall` con distintos 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). ## Proporciona 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. El LLM obtiene automáticamente la documentación adecuada 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 GitHub de Context7](https://github.com/upstash/context7). Una vez configurado, referencia la biblioteca de Adapty en tus prompts: ``` Use the adaptyteam/adapty-docs library to look up how to install the Flutter SDK ``` :::warning Aunque Context7 elimina la necesidad de pegar enlaces de documentación manualmente, el orden de implementación importa. Sigue el [recorrido de implementación](#implementation-walkthrough) paso a paso para asegurarte de que todo funciona correctamente. ::: ### Usa documentación en texto plano \{#use-plain-text-docs\} Puedes acceder a cualquier documento de Adapty como Markdown en texto plano. Añade `.md` al final de su URL o haz clic en **Copy for LLM** debajo del título del artículo. Por ejemplo: [adapty-cursor-flutter.md](https://adapty.io/docs/es/adapty-cursor-flutter.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` listos 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 el orden de implementación recomendado. Cada etapa incluye la documentación que debes enviar a tu LLM, qué deberías ver al terminar y los problemas más comunes. ### Planifica tu integración \{#plan-your-integration\} Antes de escribir 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, ya que esto afecta a las guías que debe seguir: - [**Adapty Paywall Builder**](adapty-paywall-builder): Creas los paywalls en el editor no-code de Adapty y el SDK los renderiza automáticamente. - [**Paywalls creados manualmente**](flutter-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 en la guía de inicio rápido](flutter-quickstart-paywalls). ### Instala y configura el SDK \{#install-and-configure-the-sdk\} Añade la dependencia del SDK de Adapty usando `flutter pub add` y actívalo con tu clave SDK pública. Esta es la base: nada más funciona sin ella. **Guía:** [Instalar y configurar el SDK de Adapty](sdk-installation-flutter) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/sdk-installation-flutter.md ``` :::tip[Punto de control] - **Esperado:** La app se compila y ejecuta en iOS y Android. La consola de depuración muestra el log de activación de Adapty. - **Problema frecuente:** "Public API key is missing" → comprueba que reemplazaste el marcador de posición con tu clave real desde 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)](flutter-quickstart-paywalls) - [Obtener paywalls del Paywall Builder y su configuración](flutter-get-pb-paywalls) - [Mostrar paywalls](flutter-present-paywalls) - [Gestionar eventos de paywall](flutter-handling-events) - [Responder a acciones de botones](flutter-handle-paywall-actions) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-paywalls.md - https://adapty.io/docs/es/flutter-get-pb-paywalls.md - https://adapty.io/docs/es/flutter-present-paywalls.md - https://adapty.io/docs/es/flutter-handling-events.md - https://adapty.io/docs/es/flutter-handle-paywall-actions.md ``` :::tip[Punto de control] - **Esperado:** El paywall aparece con tus productos configurados. 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 de placement coincide exactamente con el del dashboard y que el placement tiene una audiencia asignada. ::: **Guías:** - [Habilitar compras en tu paywall personalizado (inicio rápido)](flutter-quickstart-manual) - [Obtener paywalls y productos](fetch-paywalls-and-products-flutter) - [Renderizar el paywall diseñado con Remote Config](present-remote-config-paywalls-flutter) - [Realizar compras](flutter-making-purchases) - [Restaurar compras](flutter-restore-purchase) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-manual.md - https://adapty.io/docs/es/fetch-paywalls-and-products-flutter.md - https://adapty.io/docs/es/present-remote-config-paywalls-flutter.md - https://adapty.io/docs/es/flutter-making-purchases.md - https://adapty.io/docs/es/flutter-restore-purchase.md ``` :::tip[Punto de control] - **Esperado:** Tu paywall personalizado muestra los productos obtenidos desde 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 tiene productos asignados en el dashboard y que el placement tiene una audiencia. ::: **Guías:** - [Descripción general del modo Observer](observer-vs-full-mode) - [Implementar el modo Observer](implement-observer-mode-flutter) - [Reportar transacciones en el modo Observer](report-transactions-observer-mode-flutter) 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-flutter.md - https://adapty.io/docs/es/report-transactions-observer-mode-flutter.md ``` :::tip[Punto de control] - **Esperado:** Tras una compra en sandbox con 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 verificar un nivel de acceso activo y restringir el contenido premium. **Guía:** [Comprobar el estado de la suscripción](flutter-check-subscription-status) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-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 después de 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](flutter-quickstart-identify) Envía esto a tu LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/es/flutter-quickstart-identify.md ``` :::tip[Punto de control] - **Esperado:** Tras llamar a `Adapty().identify()`, 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 problemas de atribución en 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 de nivel de acceso y requisitos de privacidad. - **Problema frecuente:** Notificaciones del servidor ausentes → configura las App Store Server Notifications en **App settings → iOS SDK** y las Google Play Real-Time Developer Notifications 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`. Es 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 (p. ej., 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. Es muy grande; úsalo solo cuando necesites el panorama completo. - Subconjuntos específicos de Flutter: [`flutter-llms.txt`](https://adapty.io/docs/es/flutter-llms.txt) y [`flutter-llms-full.txt`](https://adapty.io/docs/es/flutter-llms-full.txt), que ahorran tokens en comparación con el sitio completo. --- # File: flutter-get-pb-paywalls --- --- title: "Obtener paywalls de Paywall Builder y su configuración en Flutter SDK" description: "Aprende cómo recuperar paywalls de PB en Adapty para un mejor control de suscripciones en Flutter." --- 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. :::warning El nuevo Paywall Builder requiere Flutter SDK versión 3.3.0 o superior. ::: Ten en cuenta que este tema hace referencia a paywalls personalizados con Paywall Builder. Si implementas 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-flutter). :::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 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-flutter) en tu app móvil.
## Obtener el paywall diseñado con Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Si has [diseñado 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 paywall contiene tanto qué mostrar como cómo mostrarlo. Aun así, necesitas obtener su ID a través del placement, su configuración de vista y luego presentarlo en tu app móvil. Para garantizar un rendimiento óptimo, es fundamental obtener el paywall y su [configuración de vista](flutter-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`: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | 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](flutter-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban 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 este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, sin importar la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra al desinstalarla o mediante una limpieza manual.

El SDK de Adapty almacena los paywalls localmente en dos capas: la caché actualizada regularmente descrita anteriormente y los [paywalls de respaldo](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 esté disponible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la 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 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 exceder ligeramente el tiempo especificado en `loadTimeout`, ya que la operación puede estar compuesta por distintas peticiones internamente.

Para Android: puedes crear `TimeInterval` con funciones de extensión (como `5.seconds`, donde `.seconds` es 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://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) con una lista de IDs de productos, el identificador del paywall, el Remote Config y varias otras propiedades. | ## Obtener la configuración de vista del 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 recuperar. ::: Después de obtener el paywall, comprueba si incluye una `ViewConfiguration`, lo que indica que fue creado con el Paywall Builder. Esto te indicará cómo mostrar el paywall. Si la `ViewConfiguration` está presente, trátalo como un paywall de Paywall Builder; si no, [manéjalo como un paywall de Remote Config](present-remote-config-paywalls-flutter). ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Una vez que tengas la vista, [presenta el paywall](flutter-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, obtener un paywall puede tardar más de lo deseado. En esos casos, puede que quieras mostrar un paywall por defecto para garantizar una experiencia de usuario fluida en lugar de no mostrar ninguno. Para resolver 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 el paywall diseñado con Paywall Builder](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar paywalls distintos para diferentes versiones de la app (actual y futuras), puedes encontrar dificultades. Tendrás que diseñar paywalls que soporten la versión actual (legacy) o asumir que los usuarios con esa versión podrían encontrar problemas con paywalls que no se renderizan. - **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 asumir estos inconvenientes para beneficiarte de una carga más rápida del paywall, usa el método `getPaywallForDefaultAudience` como se indica a continuación. De lo contrario, usa `getPaywall` descrito [arriba](#fetch-paywall-designed-with-paywall-builder). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note El método `getPaywallForDefaultAudience` está disponible a partir de la versión 3.2.0 del Flutter SDK. ::: | Parámetro | Presencia | Descripción | |---------|--------|-----------| | **placementId** | requerido | 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: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar datos desde el servidor y devolverá los datos en caché en caso de fallo. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban 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 este caso, los usuarios podrían no obtener los datos más recientes, pero experimentarán tiempos de carga más rápidos, sin importar la calidad de su conexión. La caché se actualiza regularmente, por lo que es seguro usarla durante la sesión para evitar peticiones de red.

Ten en cuenta que la caché se mantiene al reiniciar la app y solo se borra al desinstalarla o mediante una limpieza manual.

| ## Personalizar recursos \{#customize-assets\} Para personalizar imágenes y vídeos en tu paywall, implementa los recursos personalizados. Las imágenes y vídeos principales tienen IDs predefinidos: `hero_image` y `hero_video`. En un bundle de recursos personalizados, apuntas a estos elementos por sus IDs y personalizas 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 Flutter SDK de Adapty a la versión 3.8.0 o superior. ::: Aquí tienes un ejemplo de cómo proporcionar recursos personalizados mediante un diccionario simple: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets: , preloadProducts: preloadProducts, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Si no se encuentra un recurso, el paywall usará su apariencia por defecto. ::: ## Configurar temporizadores definidos por el desarrollador \{#set-up-developer-defined-timers\} Para usar temporizadores personalizados en tu app móvil, crea un objeto que siga el protocolo `AdaptyTimerResolver`. Este objeto define cómo debe renderizarse cada temporizador personalizado. Si lo prefieres, puedes usar directamente un diccionario `[String: Date]`, ya que ya cumple con este protocolo. Aquí tienes un ejemplo: ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2025, 1, 1), // New Year 2025 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` En este ejemplo, `CUSTOM_TIMER_NY` y `CUSTOM_TIMER_6H` son los **Timer ID** de los temporizadores definidos por el desarrollador que configuraste en el Adapty Dashboard. El `timerResolver` garantiza que tu app actualice dinámicamente cada temporizador con el valor correcto. Por ejemplo: - `CUSTOM_TIMER_NY`: El tiempo restante hasta el fin del temporizador, como el día de Año Nuevo. - `CUSTOM_TIMER_6H`: El tiempo restante en un período de 6 horas que comenzó cuando el usuario abrió el paywall. --- # File: flutter-present-paywalls --- --- title: "Flutter - Presenta paywalls del nuevo Paywall Builder" description: "Presenta paywalls en apps de Flutter usando las funciones de monetización de Adapty." --- Si has personalizado un paywall con el Paywall Builder, no necesitas preocuparte por renderizarlo en el código de tu app móvil para mostrárselo al usuario. Ese paywall contiene tanto lo que se debe mostrar como la forma en que se debe mostrar. :::warning Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder**, que requieren SDK v3.2.0 o posterior. El proceso para presentar paywalls varía según la versión del Paywall Builder con la que se hayan diseñado y para los paywalls de Remote Config. - Para presentar **paywalls de Remote Config**, consulta [Renderizar un paywall diseñado con Remote Config](present-remote-config-paywalls). ::: El SDK de Adapty para Flutter ofrece dos formas de presentar paywalls: - **Pantalla independiente** - **Widget embebido** ## Presentar como pantalla independiente \{#present-as-standalone-screen\} Para mostrar un paywall como pantalla independiente, usa el método `view.present()` en el `view` creado por el método [`createPaywallView`](flutter-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 generar un error `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // 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. ::: ### Cerrar el paywall \{#dismiss-the-paywall\} Cuando necesites cerrar el paywall mediante código, usa el método `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Mostrar diálogo \{#show-dialog\} Usa este método en lugar de los diálogos de alerta nativos cuando haya un paywall presentado en Android. En Android, las alertas normales aparecen detrás del paywall y, por tanto, son invisibles para el usuario. Este método garantiza que el diálogo se muestre correctamente encima del paywall en todas las plataformas. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle 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` (predeterminado) o `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Embeber en la jerarquía de widgets \{#embed-in-widget-hierarchy\} Para embeber un paywall dentro de tu árbol de widgets existente, usa el widget `AdaptyUIPaywallPlatformView` directamente en la jerarquía de widgets de Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIPaywallPlatformView( paywall: paywall, // The paywall object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidFailRendering: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Para que la vista de plataforma en Android funcione, asegúrate de que tu `MainActivity` extienda `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: --- # File: flutter-handle-paywall-actions --- --- title: "Responder a las acciones de botones en el SDK de Flutter" description: "Gestiona las acciones de botones en paywalls de Flutter con Adapty para mejorar la monetización de tu 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 predefinida 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. :::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. ::: ## 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 las acciones `CloseAction` y `AndroidSystemBackAction`. :::info En el SDK de Flutter, las acciones `CloseAction` y `AndroidSystemBackAction` cierran el paywall por defecto. Sin embargo, puedes sobrescribir este comportamiento en tu código si lo necesitas. Por ejemplo, cerrar un paywall podría disparar la apertura de otro. ::: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; default: break; } } ``` ## 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 de la misma manera que los botones con la acción **Open URL**. ::: Para añadir un botón que abra un enlace desde tu paywall (por ejemplo, **Terms of use** o **Privacy policy**): 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. ```dart // You have to install url_launcher plugin in order to handle urls: // https://pub.dev/packages/url_launcher void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { switch (action) { case OpenUrlAction(url: final url): final Uri uri = Uri.parse(url); launchUrl(uri, mode: LaunchMode.inAppBrowserView); break; default: break; } } ``` ## 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 la acción **Login**. 2. En el código de tu app, implementa un manejador para la acción `login` que identifique a tu usuario. ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Handle login action Navigator.of(context).push(MaterialPageRoute(builder: (context) => LoginScreen())); break; default: break; } } ``` ## 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: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another paywall break; default: break; } } ``` --- # File: flutter-handling-events --- --- title: "Flutter - Gestionar eventos del paywall" description: "Descubre cómo gestionar eventos relacionados con suscripciones en Flutter usando Adapty para rastrear las interacciones de los usuarios de manera efectiva." --- :::important Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywalls. También debes implementar el manejo de botones (cerrar paywall, abrir enlaces, etc.). Consulta nuestra [guía sobre el manejo de acciones de botones](flutter-handle-paywall-actions) para más detalles. ::: Los paywalls configurados con el [Paywall Builder](adapty-paywall-builder-legacy) no necesitan código adicional para realizar ni restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede responder. Esos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación aprenderás cómo responder a estos eventos. :::warning Esta guía es exclusivamente para **paywalls del nuevo Paywall Builder**, que requieren el SDK de Adapty v3.0 o posterior. ::: Para controlar o monitorizar los procesos que ocurren en la pantalla del paywall dentro de tu app, implementa los métodos de `AdaptyUIPaywallsEventsObserver` y establece el observer antes de presentar cualquier pantalla: ```javascript showLineNumbers title="Flutter" AdaptyUI().setPaywallsEventsObserver(this); ``` :::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. ::: ### Eventos generados por el usuario \{#user-generated-events\} #### Paywall mostrado \{#paywall-appeared\} Este método se invoca cuando la vista del paywall aparece en pantalla. :::note En iOS, 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 integrado. ::: ```javascript showLineNumbers title="Flutter" void paywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Paywall ocultado \{#paywall-disappeared\} Este método se invoca cuando la vista del paywall desaparece de la pantalla. :::note En iOS, también se invoca cuando un [web paywall](web-paywall#step-2a-add-a-web-purchase-button) abierto desde un paywall en un navegador integrado desaparece de la pantalla. ::: ```javascript showLineNumbers title="Flutter" void paywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Selección de producto \{#product-selection\} Si se selecciona un producto para comprar (por el usuario o por el sistema), se invocará este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { } ```
Ejemplo de evento (haz clic para expandir) ```javascript { "productId": "premium_monthly" } ```
#### Compra iniciada \{#started-purchase\} Si el usuario inicia el proceso de compra, se invocará este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } ```
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 finalizada \{#finished-purchase\} Este método se invoca cuando una compra tiene éxito, el usuario cancela su compra o la compra queda pendiente: ```javascript showLineNumbers title="Flutter" void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ```
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": "AdaptyPurchaseResultSuccess", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "AdaptyPurchaseResultPending" } } // User cancelled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "AdaptyPurchaseResultUserCancelled" } } ```
Recomendamos cerrar la pantalla en ese caso. Consulta [Responder a acciones de botones](flutter-handle-paywall-actions) para más detalles sobre cómo cerrar una pantalla de paywall. #### Navegación de pago web finalizada \{#finished-web-payment-navigation\} Este método se invoca después de un intento de abrir un [web paywall](web-paywall) para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos: ```javascript showLineNumbers title="Flutter" void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Parámetros:** | Parámetro | Descripción | |:------------|:---------------------------------------------------------------------------------------------------------------------| | **product** | Un `AdaptyPaywallProduct` para el que se abrió el web paywall. Puede ser `null`. | | **error** | Un objeto `AdaptyError` si la navegación al web paywall falló; `null` si la navegación fue exitosa. |
Ejemplos de eventos (haz clic para expandir) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Compra fallida \{#failed-purchase\} Este método se invoca cuando una compra falla (por ejemplo, debido a problemas de pago o errores de red). **No** se activa para cancelaciones iniciadas por el usuario ni para transacciones pendientes — esos casos los gestiona `paywallViewDidFinishPurchase`: ```javascript showLineNumbers title="Flutter" void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } ```
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" } } } ```
#### Restauración iniciada \{#started-restore\} Si el usuario inicia el proceso de restauración, se invocará este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Restauración exitosa \{#successful-restore\} Si la restauración de una compra tiene éxito, se invocará este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } ```
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" } ] } } ```
Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de suscripción](flutter-listen-subscription-changes) para aprender cómo verificarlo y el tema [Responder a acciones de botones](flutter-handle-paywall-actions) para aprender cómo cerrar una pantalla de paywall. #### Restauración fallida \{#failed-restore\} Si la restauración de una compra falla, se invocará este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
### Carga de datos y renderizado \{#data-fetching-and-rendering\} #### Errores al cargar productos \{#product-loading-errors\} Si no pasas el array de productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí mismo. Si esta operación falla, AdaptyUI reportará el error invocando este método: ```javascript showLineNumbers title="Flutter" void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } ```
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 se produce un error durante el renderizado de la interfaz, se reportará llamando a este método. Por defecto (desde v3.15.2), el paywall se cierra automáticamente cuando ocurre un error de renderizado, pero puedes cambiar este comportamiento si lo necesitas. ```javascript showLineNumbers title="Flutter" void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // Default behavior: view.dismiss() // Override with custom logic if needed, for example: // - Log the error // - Show an error message to the user } ```
Ejemplo de evento (haz clic para expandir) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
En condiciones normales, estos errores no deberían ocurrir, así que si te encuentras con alguno, por favor comunícanoslo. --- # File: flutter-use-fallback-paywalls --- --- title: "Flutter - 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" --- :::warning Los paywalls de respaldo son compatibles con Flutter SDK v2.11 y versiones posteriores. ::: 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 los archivos de configuración de respaldo al directorio `assets` de la aplicación en la raíz del proyecto. 2. Llama al método `.setFallback` **antes** de obtener el paywall o el onboarding de destino. ```javascript showLineNumbers title="javascript" final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros: | Parámetro | Descripción | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **assetId** | Ruta al archivo de configuración de respaldo. | :::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: flutter-localizations-and-locale-codes --- --- title: "Usar localizaciones y códigos de idioma en el SDK de Flutter" description: "Gestiona las localizaciones de la app y los códigos de idioma para llegar a una audiencia global." --- ## Por qué esto es importante \{#why-this-is-important\} Hay algunos casos 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 apoyamos 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 a continuación — 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 consiste en subtags en minúsculas separadas por guiones. Algunos ejemplos: `en` (inglés), `pt-br` (portugués (Brasil)), `zh` (chino simplificado), `zh-hant` (chino tradicional). ## Coincidencia de códigos de configuración regional \{#locale-code-matching\} Cuando Adapty recibe una llamada desde el SDK con el código de configuración regional y busca la localización correspondiente de un paywall, ocurre lo siguiente: 1. La cadena de configuración regional entrante se convierte a minúsculas y todos los guiones bajos (`_`) se reemplazan por guiones (`-`) 2. Se busca la localización cuyo código de configuración regional coincida exactamente 3. Si no se encuentra ninguna coincidencia, se toma la subcadena anterior al primer guión (`pt` para `pt-br`) y se busca la localización que coincida con ella 4. Si tampoco se encuentra ninguna 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 estás pensando en las localizaciones, lo más probable es que ya trabajes con archivos de cadenas localizadas en tu proyecto. En ese caso, te recomendamos añadir un par clave-valor con el código de idioma de Adapty correspondiente en cada uno de tus archivos para las localizaciones. Después, extrae el valor de esa clave al llamar a nuestro SDK, así: ```dart showLineNumbers // 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files /* app_en.arb */ "adapty_paywalls_locale": "en", /* app_es.arb */ "adapty_paywalls_locale": "es", /* app_pt_br.arb */ "adapty_paywalls_locale": "pt-br", // 2. Extract and use the locale code final locale = AppLocalizations.of(context)!.adapty_paywalls_locale; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` De esta forma tienes control total sobre qué localización se recuperará para cada usuario de tu app. ## Implementando las localizaciones: la otra forma \{#implementing-localizations-the-other-way\} Puedes obtener resultados similares (aunque no idénticos) sin definir explícitamente los códigos de idioma para cada localización. Esto implicaría extraer un código de idioma de otros objetos que tu plataforma proporciona, así: ```dart showLineNumbers final locale = Localizations.localeOf(context).languageCode; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Ten en cuenta que no recomendamos este enfoque por las siguientes razones: 1. En iOS, los idiomas preferidos y la configuración regional actual no son idénticos. Si quieres que la localización se seleccione correctamente, tendrás que apoyarte en la lógica de Apple, que funciona de forma nativa si usas el enfoque recomendado con archivos de cadenas localizadas, o bien recrearla tú mismo. 2. Es difícil predecir exactamente qué recibirá el servidor de Adapty. Por ejemplo, en iOS es posible obtener una configuración regional como `ar_OM@numbers='latn'` en el dispositivo y enviarla a nuestro servidor. Para esa llamada no obtendrás la localización `ar-om` que buscabas, sino `ar`, lo cual probablemente no es lo esperado. Aunque decidas usar este enfoque de todos modos, asegúrate de haber cubierto todos los casos de uso relevantes. --- # File: flutter-web-paywall --- --- title: "Implementar paywalls web en Flutter SDK" description: "Configura un paywall web para cobrar sin las comisiones ni las revisiones de la App Store." --- :::important Antes de empezar, asegúrate de haber [configurado tu paywall web en el dashboard](web-paywall) y de tener instalada la versión 3.6.1 o posterior del SDK de Adapty. ::: Si estás trabajando con un paywall que has desarrollado tú mismo, necesitas gestionar los paywalls web mediante el método del SDK. El método `.openWebPaywall`: 1. Genera una URL única que permite a Adapty vincular el paywall concreto mostrado a un usuario determinado con la página web a la que es redirigido. 2. Detecta cuándo los usuarios vuelven a la app y luego solicita `.getProfile` a intervalos cortos para determinar si los derechos de acceso del perfil se han actualizado. De esta forma, si el pago se ha completado correctamente y los derechos de acceso se han actualizado, la suscripción se activa en la app casi de inmediato. ```dart showLineNumbers title="Flutter" try { await Adapty().openWebPaywall(product: ); // The web paywall will be opened } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` :::note Existen dos versiones del método `openWebPaywall`: 1. `openWebPaywall(product)`, que genera URLs por paywall y añade también los datos del producto a las URLs. 2. `openWebPaywall(paywall)`, que genera URLs por 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 paywall web. ::: #### Gestionar errores \{#handle-errors\} | Error | Descripción | Acción recomendada | |-----------------------------------------|-----------------------------------------------------------------|---------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | El paywall no tiene configurada una URL de compra web | Comprueba si el paywall se ha configurado correctamente en el Adapty Dashboard | | AdaptyError.productWithoutPurchaseUrl | El producto no tiene una URL de compra web | Verifica la configuración del producto en el Adapty Dashboard | | AdaptyError.failedOpeningWebPaywallUrl | No se pudo abrir la URL en el navegador | Revisa la configuración del dispositivo o proporciona un método de compra alternativo | | AdaptyError.failedDecodingWebPaywallUrl | No se pudieron codificar correctamente los parámetros en la URL | Verifica que los parámetros de la URL sean válidos y estén correctamente formateados | ## Abrir paywalls web en un navegador in-app \{#open-web-paywalls-in-an-in-app-browser\} :::important La apertura de paywalls web en un navegador in-app está disponible a partir de la versión 3.15 del SDK de Adapty. ::: Por defecto, los paywalls web se abren en el navegador externo. Para ofrecer una experiencia de usuario fluida, puedes abrir los paywalls web en un navegador in-app. Esto muestra la página de compra web dentro de tu aplicación, permitiendo a los usuarios completar las transacciones sin cambiar de app. Para habilitarlo, establece el parámetro `in` en `.inAppBrowser`: ```dart showLineNumbers try { await Adapty().openWebPaywall( product: , openIn: AdaptyWebPresentation.inAppBrowser, ); // The web paywall will be opened in the in-app browser } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` --- # File: flutter-troubleshoot-paywall-builder --- --- title: "Solucionar problemas del Paywall Builder en el SDK de Flutter" description: "Solucionar problemas del Paywall Builder en el SDK de Flutter" --- Esta guía te ayuda a resolver los problemas más comunes al usar paywalls diseñados con el Adapty Paywall Builder en el SDK de Flutter. ## Fallo al obtener la configuración del paywall \{#getting-a-paywall-configuration-fails\} **Problema**: El método `createPaywallView` no puede recuperar la configuración del paywall. **Causa**: El paywall no está habilitado para mostrarse en el dispositivo en el Paywall Builder. **Solución**: Activa el interruptor **Show on device** en el Paywall Builder. ## El número de vistas del paywall es demasiado alto \{#the-paywall-view-number-is-too-big\} **Problema**: El recuento de vistas del paywall muestra el doble del número esperado. **Causa**: Es posible que estés llamando a `logShowPaywall` en tu código, lo que duplica el recuento de vistas si usas el Paywall Builder. En los paywalls diseñados con el Paywall Builder, el seguimiento de analytics es automático, por lo que no es necesario usar este método. **Solución**: Asegúrate de no llamar a `logShowPaywall` en tu código si estás usando el Paywall Builder. ## Otros problemas \{#other-issues\} **Problema**: Experimentas otros problemas relacionados con el Paywall Builder que no se han tratado anteriormente. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](flutter-sdk-migration-guides). Muchos problemas se resuelven en versiones más nuevas del SDK. --- # File: flutter-quickstart-manual --- --- title: "Habilitar compras en tu paywall personalizado en Flutter SDK" description: "Integra el SDK de Adapty en tus paywalls personalizados de Flutter 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](flutter-quickstart-paywalls). Con Paywall Builder, creas paywalls en un editor visual sin código, Adapty gestiona toda la lógica de compras 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) – todo lo 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 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 paywalls distintos a diferentes usuarios. Asegúrate de entender estos conceptos aunque trabajes con tu paywall personalizado. Básicamente, son 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 usuarios \{#manage-users\} Puedes trabajar con o sin autenticación de backend en tu lado. Sin embargo, el SDK de Adapty gestiona de forma diferente a los usuarios anónimos e identificados. Lee la [guía de inicio rápido de identificación](flutter-quickstart-identify) para entender las particularidades 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 ese paywall usando el método `getPaywallProducts`. ```dart showLineNumbers Future loadPaywall() async { try { final paywall = await Adapty().getPaywall(placementId: 'YOUR_PLACEMENT_ID'); final products = await Adapty().getPaywallProducts(paywall: paywall); // Use products to build your custom paywall UI } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Paso 2. Acepta 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. ```dart showLineNumbers Future purchaseProduct(AdaptyPaywallProduct product) async { try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // Purchase successful, profile updated break; case AdaptyPurchaseResultUserCancelled(): // User canceled the purchase break; case AdaptyPurchaseResultPending(): // Purchase is pending (e.g., user will pay offline with cash) break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Paso 3. Restaura compras \{#step-3-restore-purchases\} Los stores de aplicaciones exigen que todas las apps con suscripciones ofrezcan una forma de que los usuarios puedan restaurar sus compras. Llama al método `restorePurchases` cuando el usuario toque el botón de restaurar. Esto sincronizará el historial de compras con Adapty y devolverá el perfil actualizado. ```dart showLineNumbers Future restorePurchases() async { try { final profile = await Adapty().restorePurchases(); // Restore successful, profile updated } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Próximos pasos \{#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 en una implementación lista para producción, consulta el [PurchasesObserver](https://github.com/adaptyteam/AdaptySDK-Flutter/blob/master/example/lib/purchase_observer.dart) en nuestra app de ejemplo, que muestra el manejo de compras con gestión de errores adecuada, observadores de UI e integración completa del SDK. A continuación, [comprueba si los usuarios han completado su compra](flutter-check-subscription-status) para determinar si mostrar el paywall o conceder acceso a las funciones de pago. --- # File: fetch-paywalls-and-products-flutter --- --- title: "Obtener paywalls y productos para paywalls con Remote Config en Flutter SDK" description: "Obtén paywalls y productos en el SDK de Flutter de Adapty para mejorar la monetización de tus usuarios." --- Antes de mostrar paywalls con Remote Config o personalizados, necesitas obtener información sobre ellos. Ten en cuenta que este tema hace referencia a paywalls con Remote Config y personalizados. Si necesitas orientación sobre cómo obtener paywalls del Paywall Builder, consulta [Obtener paywalls del Paywall Builder y su configuración](flutter-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 (haz clic para ampliar) 1. [Crea tus productos](create-product) en el Adapty Dashboard. 2. [Crea un paywall e incorpora los productos a tu paywall](create-paywall) en el Adapty Dashboard. 3. [Crea placements e incorpora tu paywall al placement](create-placement) en el Adapty Dashboard. 4. [Instala el SDK de Adapty](sdk-installation-flutter) en tu app.
## 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 dentro de placements específicos de tu app. Para mostrar los productos, necesitas obtener un [Paywall](paywalls) de uno de tus [placements](placements) con el método `getPaywall`. :::important **No escribas los IDs de producto en el código.** El único ID que debes escribir en el código 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 un paywall devuelve dos productos hoy y tres mañana, muéstralos todos sin necesidad de cambiar el código. ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` | 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 configuración regional](flutter-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar datos del servidor y devolverá datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver datos en caché si existen. En este caso, puede que los usuarios no reciban los datos más recientes, pero experimentará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 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 paywalls en dos capas: la caché actualizada regularmente descrita arriba y los [paywalls de respaldo](flutter-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 el CDN no sea accesible. Este sistema está diseñado para garantizar que siempre obtengas la versión más reciente de tus paywalls, asegurando la 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 tiempo de espera, 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 de espera especificado en `loadTimeout`, ya que la operación puede incluir diferentes solicitudes internamente.

| ¡No escribas los IDs de producto en el código! Dado que los paywalls se configuran de forma remota, los productos disponibles, el número de productos y las ofertas especiales (como períodos de prueba gratuitos) 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 luego obtienes 3 productos, tu app debería mostrar los 3 sin necesidad de cambios en el código. Lo único que debes escribir en el código es el ID del placement. Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Un objeto [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) con: una lista de IDs de productos, el identificador del paywall, el Remote Config y varias otras propiedades. | ## Obtener productos \{#fetch-products\} Una vez que tengas el paywall, puedes consultar el array de productos que le corresponde: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Lista de objetos [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) con: identificador del producto, nombre del producto, precio, moneda, duración de la suscripción y varias otras propiedades. | Cuando implementes tu propio diseño de paywall, probablemente necesitarás acceder a estas propiedades del objeto [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). A continuación se muestran las propiedades más utilizadas, pero consulta el documento enlazado para ver todos los detalles de 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 del store seleccionado por el usuario, no en la configuración regional 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 configuración regional del dispositivo. También puedes acceder al precio como número con `product.price.amount`. El valor se proporcionará en la moneda local. Para obtener el símbolo de moneda asociado, usa `product.price.currencySymbol`. | | **Subscription Period** | Para mostrar el período (por ejemplo, semana, mes, año, etc.), usa `product.subscription?.localizedPeriod`. Esta localización se basa en la configuración regional del dispositivo. Para obtener el período de suscripción de forma programática, usa `product.subscription?.period`. Desde ahí puedes acceder al enum `unit` para obtener la duración (es decir, día, semana, mes, año o desconocido). El valor `numberOfUnits` te dará el número de unidades del período. Por ejemplo, para una suscripción trimestral, verías `AdaptyPeriodUnit.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 contiene una oferta introductoria, consulta la propiedad `product.subscription?.offer?.phases`. 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 se encuentran las siguientes propiedades útiles:
• `paymentMode`: un enum con los valores `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` y `AdaptyPaymentMode.unknown`. Las pruebas gratuitas serán del tipo `AdaptyPaymentMode.freeTrial`.
• `price`: el precio con descuento como número. Para las pruebas gratuitas, busca `0` aquí.
• `localizedNumberOfPeriods`: una cadena localizada según la configuración regional 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 la configuración regional 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 deseable. En estas situaciones, puede que quieras mostrar un paywall predeterminado para garantizar una experiencia 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`, tal como se detalla en la sección [Obtener información del paywall](fetch-paywalls-and-products-flutter#fetch-paywall-information) anterior. :::warning Por qué recomendamos usar `getPaywall` El método `getPaywallForDefaultAudience` tiene algunos inconvenientes importantes: - **Posibles problemas de compatibilidad con versiones anteriores**: Si necesitas mostrar paywalls diferentes para distintas versiones de la app (actual y futuras), puedes encontrarte con dificultades. Tendrás que diseñar paywalls compatibles con la versión actual (legacy) o aceptar que los usuarios con esa versión puedan tener problemas con paywalls que no se renderizan. - **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 (incluyendo por países, atribución de marketing o tus propios atributos personalizados). Si estás dispuesto a aceptar estos inconvenientes para beneficiarte de una obtención más rápida del paywall, usa el método `getPaywallForDefaultAudience` como se indica a continuación. En caso contrario, sigue usando el método `getPaywall` descrito [arriba](fetch-paywalls-and-products-flutter#fetch-paywall-information). ::: :::note El método `getPaywallForDefaultAudience` aún no es compatible con el SDK de Flutter, pero el soporte se añadirá próximamente. ::: | 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 configuración regional](flutter-localizations-and-locale-codes) para más información sobre los códigos de idioma y cómo recomendamos usarlos.

| | **fetchPolicy** | por defecto: `.reloadRevalidatingCacheData` |

Por defecto, el SDK intentará cargar datos del servidor y devolverá datos en caché en caso de error. Recomendamos esta opción porque garantiza que tus usuarios siempre reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen una conexión a internet inestable, considera usar `.returnCacheDataElseLoad` para devolver datos en caché si existen. En este caso, puede que los usuarios no reciban los datos más recientes, pero experimentará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 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.

| --- # File: present-remote-config-paywalls-flutter --- --- title: "Renderizar un paywall diseñado con Remote Config en el SDK de Flutter" description: "Descubre cómo presentar paywalls de Remote Config en el SDK de Flutter de Adapty para personalizar la experiencia del usuario." --- Si has personalizado un paywall usando Remote Config, necesitarás implementar el renderizado en el código de tu app para mostrárselo a los usuarios. Como Remote Config ofrece flexibilidad adaptada a tus necesidades, tú decides qué incluir y cómo se muestra la vista de tu paywall. Proporcionamos un método para obtener la configuración remota, dándote autonomía para mostrar tu paywall personalizado configurado a través de 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. ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` En este punto, una vez que hayas recibido todos los valores necesarios, es 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 los 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-flutter#track-paywall-view-events) tal como se describe a continuación, para que Adapty Analytics pueda capturar información para los embudos y las pruebas A/B. ::: Una vez que 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](flutter-making-purchases). Te recomendamos [crear un paywall de respaldo llamado fallback paywall](flutter-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 del paywall requiere tu intervención, ya que solo tú sabes cuándo un usuario ve un paywall. Para registrar un evento de visualización del 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). ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | obligatorio | Un objeto [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | --- # File: flutter-making-purchases --- --- title: "Realizar compras en una aplicación móvil con Flutter SDK" description: "Guía sobre cómo gestionar compras in-app y suscripciones con Adapty." --- Mostrar paywalls en tu aplicación móvil es un paso esencial para ofrecer a los usuarios acceso a contenido o servicios premium. Sin embargo, simplemente mostrar esos paywalls es suficiente para gestionar las compras solo si usas el [Paywall Builder](adapty-paywall-builder) para personalizarlos. Si no usas el Paywall Builder, debes utilizar un método específico llamado `.makePurchase()` para completar una compra y desbloquear el contenido deseado. Este método es la puerta de entrada para que los usuarios interactúen con los paywalls y realicen sus transacciones. Si tu paywall tiene una oferta promocional activa para el producto que el usuario intenta 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 paywalls configurados con el Paywall Builder. En otros casos, tendrás que [verificar la elegibilidad del usuario para una oferta introductoria en iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Omitir este paso puede provocar que tu app sea rechazada durante la revisión. Además, podría resultar en que se cobre el precio completo a usuarios que 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 omitir este paso. **¿Buscas instrucciones paso a paso?** Consulta la [guía de inicio rápido](flutter-implement-paywalls-manually) para obtener instrucciones de implementación completas con todo el contexto. ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Parámetros de la solicitud: | Parámetro | Presencia | Descripción | | :---------- | :-------- | :-------------------------------------------------------------------------------------------------- | | **Product** | obligatorio | Un objeto [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) obtenido del paywall. | Parámetros de la respuesta: | Parámetro | Descripción | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

Si la solicitud se ha realizado correctamente, la respuesta contiene este objeto. Un objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) 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 determinar si el usuario tiene el acceso requerido en la app.

| :::warning **Nota:** si aún utilizas 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, debes proporcionar el [secreto compartido de la App Store de Apple](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Este método está actualmente deprecado por Apple. ::: ## Cambiar la suscripción al realizar una compra \{#change-subscription-when-making-a-purchase\} Cuando un usuario opta por una nueva suscripción en lugar de renovar la actual, el comportamiento depende del store: - En la App Store, la suscripción se actualiza automáticamente dentro del grupo de suscripciones. Si un usuario compra una suscripción de un grupo mientras ya tiene activa una de otro grupo, ambas suscripciones estarán activas al mismo tiempo. - En Google Play, la suscripción no se actualiza automáticamente. Tendrás que gestionar el cambio en el código de tu aplicación móvil tal 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: ```dart showLineNumbers try { final result = await adapty.makePurchase( product: product, subscriptionUpdateParams: subscriptionUpdateParams, ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Parámetro adicional de la solicitud: | Parámetro | Presencia | Descripción | | :--------------------------- | :-------- |:--------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | obligatorio | Un objeto [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.html). | Puedes obtener más información sobre las suscripciones y los modos de reemplazo en la documentación para desarrolladores de Google: - [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. Las degradaciones no son compatibles. - 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 hoja de canje de códigos en tu app: ```dart showLineNumbers try { await Adapty().presentCodeRedemptionSheet(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` :::danger Según nuestras observaciones, la hoja de canje de códigos de oferta puede no funcionar de forma fiable en algunas apps. Recomendamos redirigir al usuario directamente a la App Store. Para ello, abre una 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 adquirir [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 dichos planes. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleEnablePendingPrepaidPlans(true), ); ``` --- # File: flutter-restore-purchase --- --- title: "Restaurar compras en una app móvil con Flutter SDK" description: "Aprende cómo restaurar compras en Adapty para garantizar una experiencia de usuario fluida." --- Restaurar compras tanto en iOS como en Android es una funcionalidad que permite a los usuarios recuperar el acceso a contenido comprado anteriormente, como suscripciones o compras in-app, sin que se les cobre de nuevo. Esta funcionalidad es especialmente útil para usuarios que hayan desinstalado y vuelto a instalar la app, o que hayan cambiado de dispositivo y quieran acceder a su contenido previamente adquirido sin pagar otra vez. :::note En los paywalls creados con [Paywall Builder](adapty-paywall-builder), las compras se restauran automáticamente sin necesidad de código adicional por tu parte. Si ese es tu caso, puedes saltarte este paso. ::: Para restaurar una compra cuando no usas [Paywall Builder](adapty-paywall-builder) para personalizar el paywall, llama al método `.restorePurchases()`: ```javascript showLineNumbers try { final profile = await Adapty().restorePurchases(); if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) { // successful access restore } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | |---------|-----------| | **Profile** |

Un objeto [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Este modelo contiene información sobre niveles de acceso, suscripciones y compras únicas.

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-flutter --- --- title: "Implementar el modo Observer en Flutter SDK" description: "Implementa el modo observer en Adapty para rastrear eventos de suscripción de usuarios en Flutter SDK." --- 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 [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 2. [Reportar transacciones](report-transactions-observer-mode-flutter) 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 la suscripción tú mismo 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. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` 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 los paywalls y las funciones de pruebas A/B de Adapty, puedes hacerlo, pero requiere algo de configuración adicional en el modo Observer. Esto es lo que necesitas hacer además de los pasos anteriores: 1. Muestra los paywalls como de costumbre para [paywalls con Remote Config](present-remote-config-paywalls-flutter). 3. [Asocia los paywalls](report-transactions-observer-mode-flutter) con las transacciones de compra. --- # File: report-transactions-observer-mode-flutter --- --- title: "Reportar transacciones en Observer Mode en Flutter SDK" description: "Reporta transacciones de compra en el Observer Mode de Adapty para obtener información sobre usuarios y seguimiento de ingresos en Flutter SDK." --- 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 explícitamente 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 análisis precisos del paywall. ```javascript showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido |
  • Para iOS: Identificador de la transacción.
  • Para Android: Identificador de cadena `purchase.getOrderId` de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación.
| | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). |
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 o restaurarlas. Es fundamental configurar esto **antes** de publicar tu app para evitar errores en los análisis. Usa `reportTransaction` en ambas plataformas para reportar explícitamente cada transacción, y usa `restorePurchases` en Android como paso adicional para asegurarte de que Adapty la reconozca. :::warning **¡No omitas el reporte de transacciones ni la restauración de compras!** Si no llamas a estos métodos, 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 análisis precisos del paywall. ```javascript showLineNumbers // every time when calling transaction.finish() if (Platform.isAndroid) { try { await Adapty().restorePurchases(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ------------- | --------- | ------------------------------------------------------------ | | transactionId | requerido |
  • Para iOS, StoreKit 1: un objeto [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
  • Para iOS, StoreKit 2: objeto [Transaction](https://developer.apple.com/documentation/storekit/transaction).
  • Para Android: Identificador de cadena (purchase.getOrderId de la compra, donde la compra es una instancia de la clase [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) de la biblioteca de facturación).
| | variationId | opcional | El identificador de cadena de la variante. Puedes obtenerlo usando la propiedad `variationId` del objeto [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). |
**Reporte de transacciones** - Las versiones hasta 3.1.x escuchan automáticamente las transacciones en el App Store, por lo que no se requiere reporte manual. - La versión 3.2 no admite el Observer Mode. **Reporte de transacciones** Usa `restorePurchases` para reportar una transacción a Adapty en el Observer Mode, tal como se explica en la página [Restaurar compras en código móvil](flutter-restore-purchase). :::warning **¡No omitas el reporte de transacciones!** Si no llamas a `restorePurchases`, Adapty no reconocerá la transacción, no aparecerá en los análisis y no se enviará a las integraciones. ::: **Asociar paywalls a transacciones** El SDK de Adapty no puede determinar el origen de las compras, ya que eres tú quien las procesa. Por lo tanto, si tienes intención de usar paywalls y/o pruebas A/B en el modo Observer, necesitas asociar la transacción proveniente de tu app store con el paywall correspondiente en el código de tu app móvil. Es importante hacerlo correctamente antes de publicar tu app; de lo contrario, generará errores en los análisis. ```javascript final transactionId = transaction.transactionIdentifier final variationId = paywall.variationId try { await Adapty().setVariationId('transactionId', variationId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ```
--- # File: flutter-troubleshoot-purchases --- --- title: "Solucionar problemas de compras en Flutter SDK" description: "Solucionar problemas de compras en Flutter SDK" --- Esta guía te ayuda a resolver los problemas más comunes al implementar compras manualmente en el Flutter SDK. ## makePurchase se ejecuta 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 dispara varias veces debido a problemas de gestión del estado de la UI o 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 modo observer \{#adaptyerror-cantmakepayments-in-observer-mode\} **Problema**: Recibes `AdaptyError.cantMakePayments` al usar `makePurchase` en modo observer. **Causa**: En modo observer, 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 observer. Tienes que elegir entre usar `makePurchase` o gestionar las compras por tu cuenta en modo observer. Consulta [Implementar el modo Observer](implement-observer-mode-flutter) 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**: Recibes 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 de facturación de Google Play 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**: Tienes problemas porque no se encuentran los `makePurchasesCompletionHandlers`. **Causa**: Esto suele estar relacionado con problemas durante las pruebas en sandbox. **Solución**: Crea un nuevo usuario sandbox e inténtalo de nuevo. Esto resuelve con frecuencia los problemas del completion handler de compras en sandbox. ## Otros problemas \{#other-issues\} **Problema**: Tienes otros problemas relacionados con compras que no están cubiertos arriba. **Solución**: Si es necesario, migra el SDK a la versión más reciente siguiendo las [guías de migración](flutter-sdk-migration-guides). Muchos problemas se resuelven en versiones más recientes del SDK. --- # File: flutter-identifying-users --- --- title: "Identificar usuarios en el SDK de Flutter" description: "Identifica usuarios en Adapty para mejorar las experiencias personalizadas de suscripción." --- Adapty crea un ID de perfil interno para cada usuario. Sin embargo, si tienes tu propio sistema de autenticación, debes 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 de servidor](getting-started-with-server-side-api), que se enviará a todas las integraciones. ### Configurar el ID de usuario del cliente durante la inicialización \{#setting-customer-user-id-on-configuration\} Si tienes un ID de usuario en el momento de la configuración, simplemente pásalo como parámetro `customerUserId` al método `.activate()`: ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) ); } catch (e) { // 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. ::: ### Configurar el ID de usuario del cliente después de la configuració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. ```dart showLineNumbers try { await Adapty().identify(customerUserId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 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()`: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` Luego puedes iniciar sesión con el usuario usando el método `.identify()`. ## Asignar `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) es un **UUID** que te permite vincular las transacciones de 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 de 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 de 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 `appAccountToken` junto con `customerUserId`. Si solo pasas el token, no se incluirá en la transacción. ::: ```dart showLineNumbers // Durante la configuración: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN") ); } catch (e) { // handle the error } // O al identificar usuarios try { await Adapty().identify(customerUserId, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### 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 de los usuarios. Estos IDs ayudan a Google Play a identificar las compras manteniendo el anonimato de la información del usuario, lo que resulta especialmente importante para la prevención del fraude y el análisis de datos. Es posible que necesites establecer estos IDs si tu app maneja datos sensibles de los usuarios o si debes cumplir con normativas de privacidad específicas. Los IDs ofuscados permiten a Google Play rastrear las compras sin exponer los identificadores reales de los usuarios. ```dart showLineNumbers // Durante la configuración: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID") ); } catch (e) { // handle the error } // O al identificar usuarios try { await Adapty().identify(customerUserId, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ## 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: flutter-setting-user-attributes --- --- title: "Establecer atributos de usuario en Flutter SDK" description: "Aprende a establecer atributos de usuario en Adapty para mejorar la segmentación de audiencias." --- Puedes añadir atributos opcionales como correo electrónico, número de teléfono, etc., al perfil del usuario de tu app. Después puedes usar esos atributos para crear [segmentos](segments) de usuarios o simplemente consultarlos en el CRM. ### Establecer atributos de usuario \{#setting-user-attributes\} Para establecer atributos de usuario, llama al método `.updateProfile()`: ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setEmail("email@email.com") ..setPhoneNumber("+18888888888") ..setFirstName('John') ..setLastName('Appleseed') ..setGender(AdaptyProfileGender.other) ..setBirthday(DateTime(1970, 1, 3)); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Ten en cuenta que los atributos que hayas establecido anteriormente 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\} A continuación se muestran las claves `` permitidas de `AdaptyProfileParameters.Builder` y sus valores ``: | Clave | Valor | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | Enum, los valores permitidos son: `female`, `male`, `other` | | birthday | Date | ### Atributos de usuario personalizados \{#custom-user-attributes\} Puedes definir tus propios atributos personalizados, que habitualmente están relacionados con el uso de tu app. Por ejemplo, en aplicaciones de fitness podrían ser el número de ejercicios por semana; en apps 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 analítica para descubrir qué métricas de producto tienen más impacto en los ingresos. ```javascript showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Para eliminar una clave existente, usa el método `.withRemoved(customAttributeForKey:)`: ```javascript showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` A veces necesitas saber qué atributos personalizados se han establecido previamente. 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 podrían haber cambiado tras la última sincronización. ::: ### Límites \{#limits\} - Hasta 30 atributos personalizados por usuario - Los nombres de clave pueden tener hasta 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 decimal con un máximo de 50 caracteres. --- # File: flutter-listen-subscription-changes --- --- title: "Comprobar el estado de suscripción en el SDK de Flutter" description: "Rastrea y gestiona el estado de suscripción de los usuarios en Adapty para mejorar la retención en tu app de Flutter." --- Con Adapty, llevar el control del estado de suscripción es muy sencillo. No tienes que insertar manualmente los IDs de producto en tu código. En su lugar, puedes confirmar fácilmente el estado de suscripción de un usuario comprobando si tiene un [nivel de acceso](access-level) activo.
Antes de empezar a comprobar el estado de suscripción (haz clic para expandir) - Para iOS, configura las [notificaciones del servidor de App Store](enable-app-store-server-notifications) - Para Android, configura las [notificaciones en tiempo real para desarrolladores (RTDN)](enable-real-time-developer-notifications-rtdn)
## Nivel de acceso y el objeto AdaptyProfile \{#access-level-and-the-adaptypro file-object\} Los niveles de acceso son propiedades del objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Te recomendamos obtener el perfil cuando tu app arranque, por ejemplo al [identificar a un usuario](flutter-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](flutter-listen-subscription-changes) más abajo. :::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()`: ```javascript showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Parámetros de respuesta: | Parámetro | Descripción | | --------- | ------------------------------------------------------------ | | Profile |

Un objeto [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). 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` devuelve el resultado más actualizado posible, ya que siempre intenta consultar la API. Si por algún motivo (por ejemplo, sin conexión a internet) el SDK de Adapty no puede obtener la información del servidor, se devolverán los datos de la caché. También es importante destacar 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 del usuario desde el que puedes obtener el estado del nivel de acceso. Puedes tener múltiples niveles de acceso por app. Por ejemplo, si tienes una app de noticias y vendes suscripciones a diferentes temáticas 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 "premium" por defecto. Aquí tienes un ejemplo para comprobar el nivel de acceso "premium" por defecto: ```javascript showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Escuchar actualizaciones del estado de suscripción \{#listening-for-subscription-status-updates\} Cada vez que cambia la suscripción del usuario, Adapty lanza un evento. Para recibir mensajes de Adapty, necesitas realizar una configuración adicional: ```javascript showLineNumbers Adapty().didUpdateProfileStream.listen((profile) { // handle any changes to subscription state }); ``` Adapty también lanza un evento al iniciar la aplicación. En ese caso, se pasará el estado de suscripción almacenado en caché. ### Caché del estado de suscripción \{#subscription-status-cache\} La caché implementada en el SDK de Adapty almacena el estado de 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 suscripción del perfil. No obstante, hay que 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: flutter-deal-with-att --- --- title: "Gestionar ATT en Flutter SDK" description: "Empieza con Adapty en Flutter para simplificar la configuración y gestión de suscripciones." --- Si tu aplicación utiliza 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. ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning 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-flutter --- --- title: "Modo Kids en Flutter SDK" description: "Activa fácilmente el Modo Kids para cumplir con las políticas de Apple y Google. Sin recopilación de IDFA, GAID ni datos de publicidad en Flutter SDK." --- Si tu aplicación Flutter está destinada a niños, debes seguir las políticas de [Apple](https://developer.apple.com/kids/) y [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 las tiendas. ## ¿Qué se requiere? \{#whats-required\} Debes configurar el SDK de Adapty para deshabilitar la recopilación de: - [IDFA (Identificador para Anunciantes)](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 con precaución. Un ID de usuario con formato `` se considerará sin duda como recopilación de datos personales, al igual que el uso del correo electrónico. Para el Modo Kids, la mejor práctica es usar identificadores aleatorios o anonimizados (por ejemplo, IDs con hash o UUIDs generados por el dispositivo) para garantizar el cumplimiento. ## Habilitar el Modo Kids \{#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 hacerlo, ve a [App settings](https://app.adapty.io/settings/general) y haz clic en **Disable IP address collection** bajo **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, deshabilita la recopilación del IDFA del usuario (para iOS), GAID/AAID (para Android) y la dirección IP. **Android: Actualiza la configuración de tu SDK** ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true), // set to `true` ..withIpAddressCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` **iOS: Habilitar el Modo Kids con CocoaPods** 1. Actualiza tu Podfile: - Si **no** tienes una sección `post_install`, añade el bloque de código completo a continuación. - Si **ya tienes** una sección `post_install`, fusiona las líneas resaltadas en ella. ```ruby showLineNumbers title="Podfile" post_install do |installer| installer.pods_project.targets.each do |target| // highlight-start if target.name == 'Adapty' target.build_configurations.each do |config| config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)'] config.build_settings['OTHER_SWIFT_FLAGS'] << '-DADAPTY_KIDS_MODE' end end // highlight-end end end ``` 2. Aplica los cambios ejecutando ```sh showLineNumbers title="Shell" pod install ``` --- # File: flutter-get-onboardings --- --- title: "Obtener onboardings en Flutter SDK" description: "Aprende cómo recuperar onboardings en Adapty para Flutter." --- Después de [diseñar la parte visual de tu onboarding](design-onboarding) con el builder en el Adapty Dashboard, puedes mostrarlo en tu app Flutter. El primer paso es obtener el onboarding asociado al placement y su configuración de vista, como se describe a continuación. Antes de empezar, asegúrate de que: 1. Tienes instalado el [SDK de Adapty para Flutter](sdk-installation-flutter) en la versión 3.8.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 builder sin código, se almacena como un contenedor con la 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 un mejor rendimiento, obtén la configuración del onboarding con antelación para dar tiempo suficiente a que las imágenes se descarguen antes de mostrárselas a los usuarios. Para obtener un onboarding, utiliza el método `getOnboarding`: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboarding(placementId: "YOUR_PLACEMENT_ID"); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` A continuación, llama al método `createOnboardingView` para obtener la vista que vas a mostrar. :::warning El resultado del método `createOnboardingView` solo se puede usar una vez. Si necesitas usarlo de nuevo, vuelve a llamar al método `createOnboardingView`. Llamarlo dos veces sin recrearlo puede producir el error `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers try { final onboardingView = await Adapty().createOnboardingView(onboarding: onboarding); } on AdaptyError catch (e) { //handle error } catch (e) { //handle 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 reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen conexiones inestables a internet, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no reciban los últimos datos, pero experimentarán tiempos de carga más rápidos independientemente de lo inestable que sea 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é 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 onboardings localmente en dos capas: la caché de actualización regular 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 última versión de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.

| | **loadTimeout** | por defecto: 5 seg |

Este valor limita el tiempo de espera de este método. Si se alcanza el tiempo límite, se devolverán los datos en caché o el respaldo local.

Ten en cuenta que en casos excepcionales este método puede agotar el tiempo de espera ligeramente después de lo especificado en `loadTimeout`, ya que la operación puede consistir en diferentes solicitudes internamente.

| Parámetros de respuesta: | Parámetro | Descripción | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Un objeto [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html) 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\} Por lo general, los onboardings se obtienen casi de forma instantánea, por lo que no es necesario preocuparse por acelerar este proceso. Sin embargo, en casos donde tienes muchas audiencias y onboardings, y tus usuarios tienen una conexión a internet débil, obtener un onboarding puede tardar más de lo deseable. 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`, 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 crear problemas al admitir varias versiones de la app, lo que requiere diseños compatibles hacia atrás o aceptar que las versiones más antiguas puedan mostrarse incorrectamente. - **Sin personalización**: Solo muestra contenido para la audiencia "All Users", eliminando la segmentación basada en país, atribución o atributos personalizados. Si una obtención más rápida supera estos inconvenientes para tu caso de uso, utiliza `getOnboardingForDefaultAudience` como se muestra a continuación. De lo contrario, usa `getOnboarding` como se describe [arriba](#fetch-onboarding). ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown 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 reciban los datos más actualizados.

Sin embargo, si crees que tus usuarios tienen conexiones inestables a internet, considera usar `.returnCacheDataElseLoad` para devolver los datos en caché si existen. En este caso, puede que los usuarios no reciban los últimos datos, pero experimentarán tiempos de carga más rápidos independientemente de lo inestable que sea 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é 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 onboardings localmente en dos capas: la caché de actualización regular 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 última versión de tus onboardings y asegurar la fiabilidad incluso cuando la conexión a internet es escasa.

| --- # File: flutter-present-onboardings --- --- title: "Presentar onboardings en Flutter 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 Flutter para mostrárselo al usuario. Ese onboarding ya contiene tanto lo que debe mostrarse como la forma en que debe hacerse. Antes de empezar, asegúrate de que: 1. Tienes instalado el [SDK de Flutter de Adapty](sdk-installation-flutter) versión 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). El SDK de Flutter de Adapty ofrece dos formas de presentar onboardings: - **Pantalla independiente** - **Widget embebido** ## Presentar como pantalla independiente \{#present-as-standalone-screen\} Para mostrar un onboarding como pantalla independiente, usa el método `onboardingView.present()` en el `onboardingView` 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 `onboardingView`. :::warning Reutilizar el mismo `onboardingView` sin recrearlo puede provocar un error `AdaptyUIError.viewAlreadyPresented`. ::: ```javascript showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Cerrar el onboarding \{#dismiss-the-onboarding\} Cuando necesites cerrar el onboarding por código, usa el método `dismiss()`: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // 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`. ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Embeber en la jerarquía de widgets \{#embed-in-widget-hierarchy\} Para embeber un onboarding dentro de tu árbol de widgets existente, usa el widget `AdaptyUIOnboardingPlatformView` directamente en la jerarquía de widgets de Flutter. ```javascript showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note Para que la platform view de Android funcione, asegúrate de que tu `MainActivity` extiende `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## Loader durante el onboarding \{#loader-during-onboarding\} Al presentar un onboarding, puede que notes una breve pantalla de carga entre tu splash screen y el onboarding mientras se inicializa la vista subyacente. Puedes gestionar esto de distintas formas según tus necesidades. #### Controlar el splash screen con onDidFinishLoading \{#control-splash-screen-using-ondidfinishloading\} :::note Este enfoque solo está disponible cuando el onboarding se embebe como widget. No está disponible para la presentación como pantalla independiente. ::: El enfoque multiplataforma recomendado es mantener visible tu splash screen o superposición personalizada hasta que el onboarding se haya cargado por completo y luego ocultarla manualmente. Al usar el widget embebido, superpón tu propio widget sobre él y oculta la superposición cuando se dispare `onDidFinishLoading`: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### Personalizar el loader nativo \{#customize-native-loader\} :::important Este enfoque es específico de plataforma y requiere mantener código de UI nativo. No se recomienda a menos que ya mantengas capas nativas separadas en tu app. ::: Si necesitas personalizar el loader predeterminado, puedes reemplazarlo con layouts específicos de cada plataforma. Este enfoque requiere implementaciones separadas para Android e iOS: - **iOS**: Añade `AdaptyOnboardingPlaceholderView.xib` a tu proyecto de Xcode - **Android**: Crea `adapty_onboarding_placeholder_view.xml` en `res/layout` y define ahí un placeholder ## Personalizar cómo se abren los enlaces en los onboardings \{#customize-how-links-open-in-onboardings\} :::important La personalización de cómo se abren los enlaces en los onboardings está disponible a partir del SDK de Adapty v.3.15.1. ::: Por defecto, los enlaces en 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 abrir los enlaces en un navegador externo, puedes personalizar este comportamiento estableciendo el parámetro `externalUrlsPresentation` en `AdaptyWebPresentation.externalBrowser`: ```dart showLineNumbers title="Flutter" final onboardingView = await AdaptyUI().createOnboardingView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser ); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` ## Desactivar los rellenos de área segura (Android) \{#disable-safe-area-paddings-android\} Por defecto, en dispositivos Android, la vista del onboarding aplica automáticamente rellenos de área segura para evitar los elementos de la UI del sistema, como la barra de estado y la barra de navegación. Sin embargo, si quieres desactivar este comportamiento y tener control total sobre el layout, puedes hacerlo añadiendo un recurso booleano a tu app: 1. Ve a `android/app/src/main/res/values`. Si no existe el archivo `bools.xml`, créalo. 2. Añade el siguiente recurso: ```xml false ``` Ten en cuenta que los cambios se aplican de forma global a todos los onboardings de tu app. --- # File: flutter-handling-onboarding-events --- --- title: "Gestionar eventos de onboarding en Flutter SDK" description: "Gestiona eventos relacionados con el onboarding en Flutter usando Adapty." --- Los onboardings configurados con el builder generan eventos a los que tu app puede responder. La forma de gestionar estos eventos depende del enfoque de presentación que estés usando: - **Presentación en pantalla completa**: Requiere configurar un observador de eventos global que gestione los eventos de todas las vistas de onboarding - **Widget embebido**: Gestiona los eventos a través de parámetros de callback inline directamente en el widget Antes de empezar, asegúrate de que: 1. Has instalado el [SDK de Flutter de Adapty](sdk-installation-flutter) 3.8.0 o posterior. 2. Has [creado un onboarding](create-onboarding). 3. Has añadido el onboarding a un [placement](placements). ## Eventos de presentación en pantalla completa \{#full-screen-presentation-events\} ### Configurar el observador de eventos \{#set-up-event-observer\} Para gestionar eventos de onboardings en pantalla completa, implementa `AdaptyUIOnboardingsEventsObserver` y configúralo antes de presentarlo: ```javascript showLineNumbers title="Flutter" AdaptyUI().setOnboardingsEventsObserver(this); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Gestionar eventos \{#handle-events\} Implementa estos métodos en tu observador: ```javascript showLineNumbers title="Flutter" void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { // Onboarding finished loading } void onboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error, ) { // Handle loading errors } void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle close action view.dismiss(); } void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle custom actions } void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle user input updates } void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { // Track analytics events } ``` ## Eventos del widget embebido \{#embedded-widget-events\} Al usar `AdaptyUIOnboardingPlatformView`, puedes gestionar eventos a través de parámetros de callback inline directamente en el widget. Ten en cuenta que los eventos se enviarán tanto a los callbacks del widget como al observador global (si está configurado), pero el observador global es opcional: ```javascript showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Onboarding finished loading }, onDidFailWithError: (error) { // Handle loading errors }, onCloseAction: (meta, actionId) { // Handle close action }, onPaywallAction: (meta, actionId) { _openPaywall(actionId); }, onCustomAction: (meta, actionId) { // Handle custom actions }, onStateUpdatedAction: (meta, elementId, params) { // Handle user input updates }, onAnalyticsEvent: (meta, event) { // Track analytics events }, ) ``` ## Tipos de eventos \{#event-types\} Las siguientes secciones describen los distintos tipos de eventos que puedes gestionar, independientemente del enfoque de presentación que estés usando. ### Gestionar acciones personalizadas \{#handle-custom-actions\} En el builder, puedes añadir una acción **custom** a un botón y asignarle un ID. Después puedes usar este 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**, el método delegado `onboardingController` se activará con el caso `.custom(id:)` y el parámetro `actionId` corresponde al **Action ID** del builder. Puedes crear tus propios IDs, como "allowNotifications". ```javascript // Full-screen presentation void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { switch (actionId) { case 'login': _login(); break; case 'allow_notifications': _allowNotifications(); break; } } // Embedded widget onCustomAction: (meta, actionId) { _handleCustomAction(actionId); } ```
Ejemplo de evento (haz clic para expandir) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
### Finalización de carga del onboarding \{#finishing-loading-onboarding\} Cuando un onboarding termina de cargarse, se activará este evento: ```javascript showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { print('Onboarding loaded: ${meta.onboardingId}'); } // Embedded widget onDidFinishLoading: (meta) { print('Onboarding loaded: ${meta.onboardingId}'); } ```
Ejemplo de evento (haz clic para expandir) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
### Cerrar el onboarding \{#closing-onboarding\} El onboarding se considera cerrado cuando el usuario pulsa un botón con la acción **Close** asignada. :::important Ten en cuenta que debes gestionar qué ocurre cuando un usuario cierra el onboarding. Por ejemplo, debes dejar de mostrar el propio onboarding. ::: ```javascript showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { await view.dismiss(); } // Embedded widget onCloseAction: (meta, actionId) { Navigator.of(context).pop(); } ```
Ejemplo de evento (haz 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 abrirlo dentro del onboarding. Si quieres abrirlo después de que el onboarding se cierre, hay una forma más directa: gestiona la acción de cierre y abre el paywall sin depender de los datos del evento. ::: Si el usuario pulsa un botón que abre un paywall, recibirás el ID de la acción del botón que [configuraste manualmente](get-paid-in-onboardings). La forma más fluida de trabajar con paywalls en onboardings es que el ID de acción sea igual al ID del placement del paywall: Ten en cuenta que, en iOS, solo se puede mostrar una vista (paywall o onboarding) en pantalla al mismo tiempo. Si presentas un paywall encima de un onboarding, no puedes controlar el onboarding en segundo plano de forma programática. Intentar cerrar el onboarding cerrará el paywall en su lugar, dejando el onboarding visible. Para evitarlo, cierra siempre la vista del onboarding antes de presentar el paywall. ```javascript showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } Future _openPaywall(String actionId) async { // Implement your paywall opening logic here } // Embedded widget onPaywallAction: (meta, actionId) { _openPaywall(actionId); } ```
Ejemplo de evento (haz clic para expandir) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
### Seguimiento de navegación \{#tracking-navigation\} Recibes un evento de analítica cuando ocurren distintos eventos relacionados con la navegación durante el flujo de onboarding: ```javascript showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { trackEvent(event.type, meta.onboardingId); } // Embedded widget onAnalyticsEvent: (meta, event) { trackEvent(event.type, meta.onboardingId); } ``` El objeto `event` puede ser uno de los siguientes tipos: | Tipo | Descripción | |------------|-------------| | `onboardingStarted` | Cuando el onboarding se ha cargado | | `screenPresented` | Cuando se muestra cualquier pantalla | | `screenCompleted` | Cuando se completa una pantalla. Incluye `elementId` opcional (identificador del elemento completado) y `reply` opcional (respuesta del usuario). Se activa cuando el usuario realiza cualquier acción para salir de la pantalla. | | `secondScreenPresented` | Cuando se muestra la segunda pantalla | | `userEmailCollected` | Se activa cuando se recopila el email del usuario a través del campo de entrada | | `onboardingCompleted` | Se activa cuando el usuario llega a una pantalla con el ID `final`. Si necesitas este evento, [asigna el ID `final` a la última pantalla](design-onboarding). | | `unknown` | Para cualquier tipo de evento no reconocido. Incluye `name` (el nombre del evento desconocido) y `meta` (metadatos adicionales) | Cada evento incluye información `meta` que contiene: | 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 |
Ejemplos de eventos (haz clic para expandir) ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
--- # File: flutter-onboarding-input --- --- title: "Procesar datos de onboardings en Flutter SDK" description: "Guarda y usa datos de onboardings en tu app Flutter 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 `onStateUpdatedAction`. Puedes guardar o procesar el tipo de campo en tu código. Por ejemplo: ```dart // Presentación a pantalla completa void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Process data } // Widget embebido onStateUpdatedAction: (meta, elementId, params) { // Process data } ``` Consulta el formato de la acción [aquí](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyUIOnboardingPlatformView/onStateUpdatedAction.html).
Ejemplos de datos guardados (el formato puede variar según tu implementación) ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "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 inmediatamente los datos introducidos con el perfil del usuario y evitar pedirle la misma información dos veces, debes [actualizar el perfil del usuario](flutter-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 nombre del usuario, y también les pides que introduzcan su correo electrónico en el campo `email`, en el código de tu app podría verse así: ```dart showLineNumbers // Presentación a pantalla completa void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Widget embebido onStateUpdatedAction: (meta, elementId, params) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` ### 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 preguntarles sobre su experiencia con el deporte 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](flutter-setting-user-attributes) para los usuarios. ```dart showLineNumbers // Presentación a pantalla completa void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Widget embebido onStateUpdatedAction: (meta, elementId, params) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` 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](flutter-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](flutter-handling-onboarding-events#opening-a-paywall). --- # File: flutter-sdk-call-order --- --- title: "Orden de llamadas en el SDK de Flutter" description: "Evita perder el acceso premium, la atribución y los errores intermitentes #2002 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 se resuelva, el SDK no tiene estado. Cualquier llamada realizada antes o en paralelo con `activate()` falla con [`#2002 notActivated`](error-handling-on-flutter-react-native-unity#custom-network-codes). Si tu app autentica usuarios y recopilas un customer user ID después del lanzamiento, llama a `Adapty().identify()` en ese momento. No llames a métodos de acción de usuario hasta que `identify` se resuelva. Las llamadas que compiten con él fallan con [`#3006 profileWasChanged`](error-handling-on-flutter-react-native-unity#custom-network-codes), o aterrizan en el 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 sigue trabajando con el perfil anónimo. Los SDKs de MMP y analítica (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 aterriza en 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 camino depende de dos factores: cuándo conoces el customer user ID y si usas un SDK de MMP o analítica. - **Pasos 2 y 5**: Obligatorios para todas las apps. Activa el SDK y luego llama a los métodos del SDK. - **Pasos 1 y 3**: Solo son necesarios si integras un SDK de MMP o analítica (AppsFlyer, Adjust, Branch, PostHog). - **Paso 4**: Solo es necesario si tu app autentica usuarios y recopila el customer user ID después del lanzamiento. Si tienes el customer user ID en el lanzamiento de la app, pásalo directamente a `activate()` (paso 2a). Este camino nunca crea un perfil anónimo, por lo que el paso 4 es innecesario. | Paso | Llamada | Cuándo | Notas | |------|----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Inicializa tu SDK de MMP o analítica (AppsFlyer, Adjust, PostHog, Branch) | Al lanzar la app, lo primero | Espera el callback de UID del MMP, por ejemplo `getAppsFlyerUID`. | | 2a | `Adapty().activate(configuration: ...)` con `withCustomerUserId` configurado en la configuración | Al lanzar 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: ...)` sin `withCustomerUserId` | Al lanzar la app, después del paso 1, si no tienes el customer user ID (o nunca lo recopilas) | Adapty crea un perfil anónimo. | | 3 | `Adapty().setIntegrationIdentifier(key: ..., value: ...)` para cada MMP | Después del paso 2, antes de cualquier llamada de acción de usuario | Necesario para que los IDs de MMP aterricen en el perfil correcto. | | 4 | `await Adapty().identify(customerUserId)` | Después del paso 3 (o paso 2 si no hay MMP), antes del paso 5 — solo en el camino 2b con autenticación | Siempre usa `await`. Las llamadas concurrentes durante `identify` producen `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | Después del paso 4 si llamas a `identify`; si no, después del paso 3 (o paso 2 si no hay MMP) | Estas llamadas necesitan un perfil estable. | :::important Omitir estos pasos provoca la pérdida del acceso premium para usuarios que regresan, la ausencia de `appsflyer_id` en los perfiles y paywalls devueltos contra la audiencia incorrecta. ::: ## Instalaciones web2app y embudo web \{#web2app-and-web-funnel-installs\} Si los usuarios compran en un checkout web (Stripe, Paddle) e instalan después la app nativa, 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 a `activate()`. 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: flutter-optimize-paywall-fetching --- --- title: "Optimizar la carga de paywalls en Flutter SDK" description: "Carga paywalls de Adapty de forma fiable: tiempos, caché y patrones de respaldo para Flutter." --- Una carga fiable de paywalls en Flutter hace tres cosas: renderiza rápido, devuelve el paywall con la audiencia correcta y recurre a un respaldo cuando la red es lenta. Las reglas a continuación cubren los tiempos, la caché y los patrones de respaldo para lograrlo. :::tip Las reglas asumen que `Adapty().activate()` y `Adapty().identify()` ya se han resuelto. Consulta [Orden de llamadas en Flutter SDK](flutter-sdk-call-order). ::: ## Reglas y errores comunes \{#rules-and-pitfalls\} | Haz esto | No hagas esto | Por qué | |---|---|---| | Carga el placement que vas a mostrar. | Precarga todos los placements de forma concurrente al arrancar. | La precarga masiva bloquea el hilo principal y produce una pantalla en negro durante la ráfaga. | | Llama a `getPaywall` después de que la atribución haya tenido tiempo de resolverse — por ejemplo, 1–2 segundos después de `activate` o cuando se dispare `didUpdateProfileStream`. | Llama a `getPaywall` en `main()` antes de `runApp`. | 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. | Esperar indefinidamente a `getPaywall`. | Sin un tiempo de espera, 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-flutter) para la referencia de los parámetros `fetchPolicy` y `loadTimeout`, y [Placements](placements) para elegir el placement adecuado. ## Ajuste para mala conectividad \{#tune-for-poor-connectivity\} Para mercados con conectividad consistentemente deficiente (zonas rurales, transporte, regiones afectadas por enrutamiento): - Establece `fetchPolicy: AdaptyPaywallFetchPolicy.returnCacheDataElseLoad` en cada carga excepto la primera. - Configura un [paywall de respaldo](fallback-paywalls) para cada placement en el Adapty Dashboard. - Establece `loadTimeout` en 3–5 segundos y acepta el respaldo cuando se agote el tiempo. - No condicionales la visualización del paywall a `getProfile()`. Llama a `getPaywall` de forma independiente para que un perfil lento no bloquee la interfaz. --- # File: flutter-test --- --- title: "Test & release in Flutter SDK" description: "Aprende cómo comprobar el estado de suscripción en tu app Flutter con Adapty." --- Si ya has implementado el SDK de Adapty en tu app Flutter, querrás comprobar que todo está configurado correctamente y que las compras funcionan como se espera en las plataformas iOS y Android. Esto implica probar tanto la integración del SDK como el flujo de compra real con el entorno sandbox de Apple y el entorno de pruebas de Google Play. ## Prueba tu app \{#test-your-app\} Para realizar pruebas exhaustivas de tus compras in-app, consulta nuestras guías de pruebas por plataforma: [guía de pruebas para iOS](test-purchases-in-sandbox) y [guía de pruebas para Android](testing-on-android). ## Prepárate para el lanzamiento \{#prepare-for-release\} Antes de enviar tu app al store, sigue la [lista de verificación para el 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: InvalidProductIdentifiers-flutter --- --- title: "Solución para el error Code-1000 noProductIDsFound en Flutter SDK" description: "Resuelve errores de identificador de producto invá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 su compra en la App Store, aunque estén listados allí. Este error puede aparecer en ocasiones acompañado de una advertencia `InvalidProductIdentifiers`. Si la advertencia aparece sin un error, puedes ignorarla sin problema. Si encuentras 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 suscripciones. 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**. 4. Compara el ID del producto de 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 suscripciones 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 suscripciones. 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**, tu **Bank Account** y tus **Tax forms** aparezcan como **Active**. Siguiendo estos pasos, deberías poder resolver la advertencia `InvalidProductIdentifiers` y tener tus productos disponibles en la store. ## Paso 6. Recrea el producto si está bloqueado \{#step-6-recreate-the-product-if-its-stuck\} Es posible que los pasos 1–5 pasen todos correctamente —estado `Approved`, Bundle ID coincidente, API key válida— y aun así el SDK devuelva `1000 noProductIDsFound`. En ese caso, puede que el producto esté bloqueado en el registro de Apple. El registro de productos de Apple entra ocasionalmente en un estado en el que un producto existe en la interfaz de App Store Connect pero no está expuesto en 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-flutter --- --- title: "Solución al error Code-1003 cantMakePayment en Flutter SDK" description: "Resuelve el error de realización de pagos al gestionar 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: flutter-migration-guide-310 --- --- title: "Guía de migración al SDK de Adapty para Flutter 3.10.0" description: "" --- El SDK de Adapty 3.10.0 es una versión principal que incorpora mejoras que, sin embargo, pueden requerir algunos pasos de migración por tu parte: 1. Actualiza el método `makePurchase` para usar `AdaptyPurchaseParameters` en lugar de parámetros individuales. 2. Reemplaza `vendorProductIds` por `productIdentifiers` en el modelo `AdaptyPaywall`. ## Actualizar el método makePurchase \{#update-makepurchase-method\} El método `makePurchase` ahora usa `AdaptyPurchaseParameters` en lugar de los argumentos individuales `subscriptionUpdateParams` e `isOfferPersonalized`. Esto proporciona mayor seguridad de tipos y permite una mayor extensibilidad de los parámetros de compra en el futuro. ```diff showLineNumbers - final purchaseResult = await adapty.makePurchase( - product: product, - subscriptionUpdateParams: subscriptionUpdateParams, - isOfferPersonalized: true, - ); + final parameters = AdaptyPurchaseParametersBuilder() + ..setSubscriptionUpdateParams(subscriptionUpdateParams) + ..setIsOfferPersonalized(true) + ..setObfuscatedAccountId('your-account-id') + ..setObfuscatedProfileId('your-profile-id'); + final purchaseResult = await adapty.makePurchase( + product: product, + parameters: parameters.build(), + ); ``` Si no necesitas parámetros adicionales, puedes usar simplemente: ```dart showLineNumbers final purchaseResult = await adapty.makePurchase( product: product, ); ``` ## Actualizar el uso del modelo AdaptyPaywall \{#update-adaptyp-aywall-model-usage\} La propiedad `vendorProductIds` ha quedado obsoleta en favor de `productIdentifiers`. La nueva propiedad devuelve objetos `AdaptyProductIdentifier` en lugar de cadenas de texto simples, lo que ofrece información de producto con una estructura más organizada. ```diff showLineNumbers - paywall.vendorProductIds.map((vendorId) => - ListTextTile(title: vendorId) - ).toList() + paywall.productIdentifiers.map((productId) => + ListTextTile(title: productId.vendorProductId) + ).toList() ``` El objeto `AdaptyProductIdentifier` proporciona acceso al ID del producto del proveedor a través de la propiedad `vendorProductId`, manteniendo la misma funcionalidad y ofreciendo una mejor estructura para mejoras futuras. ## Compatibilidad con versiones anteriores \{#backward-compatibility\} Ambos cambios mantienen la compatibilidad con versiones anteriores: - Los parámetros antiguos en `makePurchase` están obsoletos, pero siguen funcionando - La propiedad `vendorProductIds` está obsoleta, pero sigue siendo accesible - El código existente seguirá funcionando, aunque verás advertencias de obsolescencia Te recomendamos actualizar tu código para usar las nuevas API y garantizar la compatibilidad futura, además de aprovechar la mayor seguridad de tipos y extensibilidad. --- # File: flutter-migration-guide-38 --- --- title: "Migrar el SDK de Adapty para Flutter a v. 3.8" description: "Migra al SDK de Adapty para Flutter v3.8 para obtener mejor rendimiento y nuevas funciones de monetización." --- El SDK 3.8.0 de Adapty es una versión mayor que incluye mejoras que pueden requerir algunos pasos de migración por tu parte. 1. Actualiza los nombres de la clase observadora y sus métodos. 2. Actualiza el nombre del método de paywalls de respaldo. 3. Actualiza el nombre de la clase de vista en los métodos de manejo de eventos. ## Actualiza los nombres de la clase observadora y sus métodos \{#update-observer-class-and-method-names\} La clase observadora y su método de registro han sido renombrados: ```diff showLineNumbers - class MyObserver extends AdaptyUIObserver { + class MyObserver extends AdaptyUIPaywallsEventsObserver { @override void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { // Handle action } } // Register observer - AdaptyUI().setObserver(this); + AdaptyUI().setPaywallsEventsObserver(this); ``` ## Actualiza el nombre del método de paywalls de respaldo \{#update-fallback-paywalls-method-name\} El método para configurar los paywalls de respaldo ha sido simplificado: ```diff showLineNumbers try { - await Adapty.setFallbackPaywalls(assetId); + await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Actualiza el nombre de la clase de vista en los métodos de manejo de eventos \{#update-view-class-name-in-event-handling-methods\} Todos los métodos de manejo de eventos ahora usan la nueva clase `AdaptyUIPaywallView` en lugar de `AdaptyUIView`: ```diff showLineNumbers - void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) + void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) - void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile) + void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile) - void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error) + void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) - void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile) + void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) - void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) - void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) + void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) - void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) ``` --- # File: migration-to-flutter-sdk-34 --- --- title: "Migrar Adapty Flutter SDK a la v. 3.4" description: "Migra al Adapty Flutter SDK v3.4 para mejor rendimiento y nuevas funciones de monetización." --- Adapty SDK 3.4.0 es una versión mayor que introduce mejoras que requieren pasos de migración por tu parte. ## Actualizar los archivos de paywall de respaldo \{#update-fallback-paywall-files\} Actualiza tus archivos de paywall de respaldo para garantizar la compatibilidad con la nueva versión del SDK: 1. [Descarga los archivos de paywall de respaldo actualizados](fallback-paywalls) desde el Adapty Dashboard. 2. [Reemplaza los paywalls de respaldo existentes en tu app](flutter-use-fallback-paywalls) con los nuevos archivos. ## Actualizar la implementación del modo Observer \{#update-implementation-of-observer-mode\} Si usas el modo Observer, asegúrate de actualizar su implementación. Anteriormente, se usaban distintos métodos para reportar transacciones a Adapty. En la nueva versión, el método `reportTransaction` debe usarse de forma consistente tanto en Android como en iOS. Este método reporta explícitamente cada transacción a Adapty, asegurando que sea reconocida. Si se usó un paywall, pasa el ID de variación para vincular la transacción con él. :::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. ::: ```diff showLineNumbers - // every time when calling transaction.finish() - if (Platform.isAndroid) { - try { - await Adapty().restorePurchases(); - } on AdaptyError catch (adaptyError) { - // handle the error - } catch (e) { - } - } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter330 --- --- title: "Migrar el SDK de Adapty Flutter a v. 3.3" description: "Migra al SDK de Adapty Flutter v3.3 para mejor rendimiento y nuevas funcionalidades de monetización." --- Adapty SDK 3.3.0 es una versión mayor que incluye mejoras que pueden requerir algunos pasos de migración de tu parte. 1. Actualiza el método para proporcionar paywalls de respaldo. 2. Elimina el método `getProductsIntroductoryOfferEligibility`. 3. Actualiza las configuraciones de integración para Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase y Google Analytics, Mixpanel, OneSignal, Pushwoosh. 4. Actualiza la implementación del modo Observer. ## Actualiza el método para proporcionar paywalls de respaldo \{#update-method-for-providing-fallback-paywalls\} Anteriormente, el método requería el paywall de respaldo como una cadena JSON (`jsonString`), pero ahora recibe la ruta al archivo de respaldo local (`assetId`). ```diff showLineNumbers import 'dart:async' show Future; import 'dart:io' show Platform; -import 'package:flutter/services.dart' show rootBundle; -final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; -final jsonString = await rootBundle.loadString(filePath); +final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { - await adapty.setFallbackPaywalls(jsonString); + await adapty.setFallbackPaywalls(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Para ver el ejemplo de código completo, consulta la página [Usar paywalls de respaldo](flutter-use-fallback-paywalls). ## Elimina el método `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} Antes del SDK de Adapty iOS 3.3.0, el objeto de producto siempre incluía las ofertas, independientemente de si el usuario era elegible. Tenías que verificar la elegibilidad manualmente antes de usar la oferta. Ahora, el objeto de producto solo incluye una oferta si el usuario es elegible. Esto significa que ya no necesitas verificar la elegibilidad: si hay una oferta presente, el usuario es elegible. ## Actualiza la configuración del SDK de integraciones de terceros \{#update-third-party-integration-sdk-configuration\} Para garantizar que las integraciones funcionen correctamente con el SDK de Adapty Flutter 3.3.0 y versiones posteriores, actualiza las configuraciones de tu SDK para las siguientes integraciones tal como se describe en las secciones a continuación. ### Adjust \{#adjust\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_config.dart'; try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } + await Adapty().setIntegrationIdentifier( + key: "adjust_device_id", + value: adid, + ); final attributionData = await Adjust.getAttribution(); var attribution = Map(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; - Adapty().updateAttribution( - attribution, - source: AdaptyAttributionSource.adjust, - networkUserId: adid, - ); + await Adapty().updateAttribution(attribution, source: "adjust"); } catch (e) { // handle the error } on AdaptyError catch (adaptyError) { // handle the error } ``` ### AirBridge \{#airbridge\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import 'package:airbridge_flutter_sdk/airbridge_flutter_sdk.dart'; final deviceUUID = await Airbridge.state.deviceUUID; try { - final builder = AdaptyProfileParametersBuilder() - ..setAirbridgeDeviceId(deviceUUID); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "airbridge_device_id", + value: deviceUUID, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Amplitude \{#amplitude\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import 'package:amplitude_flutter/amplitude.dart'; final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); final deviceId = await amplitude.getDeviceId(); final userId = await amplitude.getUserId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setAmplitudeDeviceId(deviceId) - ..setAmplitudeUserId(userId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "amplitude_user_id", + value: userId, + ); + await Adapty().setIntegrationIdentifier( + key: "amplitude_device_id", + value: deviceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### AppMetrica \{#appmetrica\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import 'package:appmetrica_plugin/appmetrica_plugin.dart'; final deviceId = await AppMetrica.deviceId; if (deviceId != null) { try { - final builder = AdaptyProfileParametersBuilder() - ..setAppmetricaDeviceId(deviceId) - ..setAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceId, + ); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID", + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` ### AppsFlyer \{#appsflyer\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import 'package:appsflyer_sdk/appsflyer_sdk.dart'; AppsflyerSdk appsflyerSdk = AppsflyerSdk(); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); - await Adapty().updateAttribution( - data, - source: AdaptyAttributionSource.appsflyer, - networkUserId: appsFlyerUID, - ); + await Adapty().setIntegrationIdentifier( + key: "appsflyer_id", + value: appsFlyerUID, + ); + + await Adapty().updateAttribution(data, source: "appsflyer"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` ### Branch \{#branch\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers FlutterBranchSdk.initSession().listen((data) async { try { + await Adapty().setIntegrationIdentifier( + key: "branch_id", + value: , + ); - await Adapty().updateAttribution(data, source: AdaptyAttributionSource.branch); + await Adapty().updateAttribution(data, source: "branch"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ); ``` ### Firebase y Google Analytics \{#firebase-and-google-analytics\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Firebase y Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; try { - final builder = AdaptyProfileParametersBuilder() - ..setFirebaseAppInstanceId(appInstanceId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Mixpanel \{#mixpanel\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setMixpanelUserId(distinctId); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "mixpanel_user_id", + value: distinctId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### OneSignal \{#onesignal\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers OneSignal.shared.setSubscriptionObserver((changes) { final playerId = changes.to.userId; if (playerId != null) { - final builder = - AdaptyProfileParametersBuilder() - ..setOneSignalPlayerId(playerId); - // ..setOneSignalSubscriptionId(playerId); try { - Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId, + ); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle error } } }); ``` ### Pushwoosh \{#pushwoosh\} Actualiza el código de tu app como se muestra a continuación. Para ver el ejemplo de código completo, consulta la [configuración del SDK para la integración con Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; - final builder = AdaptyProfileParametersBuilder() - ..setPushwooshHWID(hwid); try { - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: hwid, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Actualiza la implementación del modo Observer \{#update-observer-mode-implementation\} Actualiza la forma en que vinculas los paywalls a las transacciones. Anteriormente, usabas el método `setVariationId` para asignar el `variationId`. Ahora puedes incluir el `variationId` directamente al registrar la transacción usando el nuevo método `reportTransaction`. Consulta el ejemplo de código final en [Asociar paywalls con transacciones de compra en el modo Observer](report-transactions-observer-mode-flutter). :::warning No olvides registrar la transacción usando el método `reportTransaction`. Si omites este paso, Adapty no reconocerá la transacción, no otorgará niveles de acceso, no la incluirá en los análisis ni la enviará a las integraciones. ¡Este paso es esencial! ::: ```diff showLineNumbers try { - await Adapty().setVariationId("YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID"); + // every time when calling transaction.finish() + await Adapty().reportTransaction( + "YOUR_TRANSACTION_ID", + variationId: "PAYWALL_VARIATION_ID", // optional + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter-sdk-v3 --- --- title: "Migrar el SDK de Adapty Flutter a la v. 3.0" description: "Migra al SDK de Adapty Flutter v3.0 para mejorar el rendimiento y acceder a nuevas funciones de monetización." --- El SDK de Adapty v.3.0 incorpora soporte para el nuevo e innovador [Adapty Paywall Builder](adapty-paywall-builder), la nueva versión de la herramienta sin código y fácil de usar para crear paywalls. Con su máxima flexibilidad y ricas capacidades de diseño, tus paywalls serán más efectivos y rentables. :::info Ten en cuenta que la librería AdaptyUI ha quedado obsoleta y ahora está incluida como parte del SDK de Adapty. ::: ## Eliminar el SDK de AdaptyUI \{#remove-adaptyui-sdk\} 1. AdaptyUI pasa a ser un módulo dentro del SDK de Adapty, así que elimina `adapty_ui_flutter` de tu archivo `pubspec.yaml`: ```diff showLineNumbers dependencies: + adapty_flutter: ^3.2.1 - adapty_flutter: ^2.10.3 - adapty_ui_flutter: ^2.1.3 ``` 2. Ejecuta: ```bash showLineNumbers title="Bash" flutter pub get ``` ## Configurar los SDK de Adapty \{#configure-adapty-sdks\} Antes era necesario usar los archivos `Adapty-Info.plist` y `AndroidManifest.xml` para configurar el SDK de Adapty. Ahora ya no es necesario usar archivos adicionales. En su lugar, puedes proporcionar todos los parámetros requeridos durante la activación. Solo tienes que configurar el SDK de Adapty una vez, normalmente al inicio del ciclo de vida de tu app. ### Activar el módulo Adapty del SDK de Adapty \{#activate-adapty-module-of-adapty-sdk\} 1. Elimina la importación del SDK de AdaptyUI de tu aplicación de la siguiente manera: ```diff showLineNumbers import 'package:adapty_flutter/adapty_flutter.dart'; - import 'package:adapty_ui_flutter/adapty_ui_flutter.dart'; ``` 2. Actualiza la activación del SDK de Adapty así: ```diff showLineNumbers try { - Adapty().activate(); + await Adapty().activate( + configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') + ..withLogLevel(AdaptyLogLevel.debug) + ..withObserverMode(false) + ..withCustomerUserId(null) + ..withIdfaCollectionDisabled(false) + ..withIpAddressCollectionDisabled(false), + ); } catch (e) { // handle the error } ``` Parámetros: | Parámetro | Presencia | Descripción | | ----------------------------------- | --------- | ------------------------------------------------------------ | | **PUBLIC_SDK_KEY** | requerido | La clave que puedes encontrar en el campo **Public SDK key** de la configuración de tu app en Adapty: [**App settings**-> pestaña **General** -> subsección **API keys**](https://app.adapty.io/settings/general) | | **withLogLevel** | opcional | Adapty registra errores y otra información relevante para dar visibilidad sobre el funcionamiento de tu app. Los niveles disponibles son:
  • error: Solo se registran errores.
  • warn: Se registran errores y mensajes del SDK que no causan errores críticos pero merecen atención.
  • info: Se registran errores, advertencias y mensajes informativos importantes, como los del ciclo de vida de los distintos módulos.
  • verbose: Se registra cualquier información adicional que pueda ser útil durante la depuración, como llamadas a funciones, consultas a la API, etc.
| | **withObserverMode** | opcional |

Un valor booleano que controla el [modo Observer](observer-vs-full-mode). Actívalo si gestionas las compras y el estado de las suscripciones tú mismo y usas Adapty solo para enviar eventos de suscripción y analíticas.

El valor predeterminado es `false`.

🚧 Al ejecutarse en modo Observer, el SDK de Adapty no cerrará ninguna transacción, así que asegúrate de gestionarlas tú.

| | **withCustomerUserId** | opcional | Un identificador del usuario en tu sistema. Lo enviamos en eventos de suscripción y analíticos para atribuir los eventos al perfil correcto. También puedes buscar clientes por `customerUserId` en el menú [**Profiles and Segments**](https://app.adapty.io/profiles/users). | | **withIdfaCollectionDisabled** | opcional |

Ponlo en `true` para deshabilitar la recopilación y el uso compartido del IDFA.

la dirección IP del usuario.

El valor predeterminado es `false`.

Para más detalles sobre la recopilación del IDFA, consulta la sección [Integración de analíticas](analytics-integration#disable-collection-of-advertising-identifiers).

| | **withIpAddressCollectionDisabled** | opcional |

Ponlo en `true` para deshabilitar la recopilación y el uso compartido de la dirección IP del usuario.

El valor predeterminado es `false`.

| ### Activar el módulo AdaptyUI del SDK de Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Solo necesitas configurar el módulo AdaptyUI si planeas usar el [Paywall Builder](adapty-paywall-builder): ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer: , ); } catch (e) { // handle the error } ``` Ten en cuenta que la configuración de AdaptyUI es opcional; puedes activar el módulo AdaptyUI sin su configuración. Sin embargo, si usas la configuración, todos los parámetros son obligatorios. Parámetros: | Parámetro | Presencia | Descripción | | :------------------------------ | :-------- | :----------------------------------------------------------- | | **memoryStorageTotalCostLimit** | requerido | Límite de coste total del almacenamiento en bytes. | | **memoryStorageCountLimit** | requerido | Límite de número de elementos del almacenamiento en memoria. | | **diskStorageSizeLimit** | requerido | Límite de tamaño de archivo en disco del almacenamiento en bytes. 0 significa sin límite. | --- # End of Documentation _Generated on: 2026-05-22T06:52:52.270Z_ _Successfully processed: 40/40 files_