Activar compras con Flow y Paywall Builder en el SDK de Flutter
Para habilitar las compras in-app, necesitas entender tres conceptos clave:
- Productos – todo lo que los usuarios pueden comprar (suscripciones, consumibles, acceso de por vida)
- Flows – secuencias de pantallas que presentan productos a los usuarios, creadas con el Flow & Paywall Builder sin código. El SDK los recupera mediante
getFlow. Si prefieres construir la interfaz en tu propio código, usa un paywall en su lugar — consulta Implementar paywalls manualmente. - Placements – dónde y cuándo muestras flows en tu app (por ejemplo,
main,onboarding,settings). Asocias los flows a 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 flows diferentes a distintos usuarios.
Adapty te ofrece tres formas de habilitar compras en tu app. Selecciona la que mejor se adapte a los requisitos de tu aplicación:
| Implementación | Complejidad | Cuándo usarla |
|---|---|---|
| Adapty Flow & Paywall Builder | ✅ Fácil | Creas un flow completo y listo para compras en el editor no-code. Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones de forma transparente. |
| Paywalls creados manualmente | 🟡 Media | Implementas la interfaz del paywall en el código de tu app, pero sigues obteniendo el objeto flow de Adapty para mantener flexibilidad en la oferta de productos. Consulta la guía. |
| Modo Observer | 🔴 Difícil | Ya tienes tu propia infraestructura de gestión de compras y quieres seguir usándola. Ten en cuenta que el modo Observer tiene sus limitaciones en Adapty. Consulta el artículo. |
Los pasos a continuación muestran cómo implementar un flow creado en el Adapty Flow & Paywall Builder.
Si prefieres construir la UI del paywall tú mismo, consulta Implementar paywalls manualmente.
Para mostrar un flow creado en el Adapty Flow & Paywall Builder, en el código de tu app solo necesitas:
- Obtén el flow: Obtenlo desde Adapty.
- Muéstralo y Adapty gestionará las compras por ti: Muestra la vista en tu app.
- Gestiona las acciones de los botones: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abre enlaces o cierra el flow cuando los usuarios pulsen botones.
Antes de empezar
Antes de empezar, completa estos pasos:
- Conecta tu app al App Store y/o a Google Play en el Adapty Dashboard.
- Crea tus productos en Adapty.
- Crea un flow y añade productos a él.
- Crea un placement y añade tu flow a él.
- Instala y activa el SDK de Adapty en el código de tu app. Esta guía usa las APIs del SDK de Adapty Flutter v4.
La forma más rápida de completar estos pasos es seguir la guía de inicio rápido o crear paywalls y placements usando la CLI para desarrolladores.
1. Obtén el flow
Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten mostrar distintos flows para diferentes audiencias o ejecutar pruebas A/B.
Para obtener un flow creado en el Adapty Flow & Paywall Builder, debes:
-
Obtener el objeto
flowpor el ID del placement usando el métodogetFlowy comprobar si fue creado en el builder mediante la propiedadhasViewConfiguration. -
Crear la vista del flow usando el método
createFlowView. La vista contiene los elementos de interfaz y los estilos necesarios para mostrar el flow.
Para obtener la configuración de la vista, publica el flow. Un flow con cambios sin publicar tiene el estado Dirty, y su placement continúa sirviendo la última versión publicada.
try {
// the requested flow
final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID');
final view = await AdaptyUI().createFlowView(
flow: flow,
);
} on AdaptyError catch (adaptyError) {
// handle the error
} catch (e) {
// handle the error
}
2. Mostrar el flow
Ahora que tienes la vista del flow, basta con añadir unas pocas líneas para mostrarlo.
Para mostrar el flow, usa el método view.present() en la view creada por el método createFlowView. Cada view solo puede presentarse una vez: cuando la cierras, la vista se libera de la memoria. Si necesitas mostrar el flow de nuevo, llama a createFlowView una vez más para crear una nueva instancia de view.
try {
await view.present();
} on AdaptyError catch (e) {
// handle the error
} catch (e) {
// handle the error
}
Para más detalles sobre cómo mostrar un flow, consulta nuestra guía.
3. Gestionar las acciones de los botones
Cuando los usuarios pulsan botones en el flow, el SDK de Flutter gestiona automáticamente las compras, la restauración, el cierre de la vista y la apertura de URLs. 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 flow, implementa los métodos de AdaptyUIFlowsEventsObserver y establece el observer antes de mostrar cualquier pantalla. Si un usuario ha realizado alguna acción, se invocará flowViewDidPerformAction y tu app deberá responder según el ID de la acción.
Tres métodos del observador son obligatorios: flowViewDidFinishPurchase, flowViewDidFinishRestore y flowViewDidReceiveError — la clase no compilará sin ellos.
Implementa el observer como un objeto dedicado y de larga duración, no como un widget. Dado que hay un único slot global de observer compartido en toda la app, vincularlo a un State provocaría una fuga de memoria (el SDK mantiene una referencia fuerte a él) y sería reemplazado silenciosamente cuando la siguiente pantalla se registre. Usar extends también hereda el comportamiento por defecto del SDK, de modo que, además de los tres métodos obligatorios, solo necesitas sobreescribir los callbacks que te interesen.
// A dedicated, long-lived handler for flow events.
// It does NOT live inside a Widget/State, so it never leaks and is never
// silently replaced when screens are pushed or popped.
class FlowEventsHandler extends AdaptyUIFlowsEventsObserver {
// A single, app-wide instance — same idiom as Adapty() and AdaptyUI().
static final FlowEventsHandler _instance = FlowEventsHandler._();
factory FlowEventsHandler() => _instance;
FlowEventsHandler._();
// This method is called when user performs an action on the flow UI.
// Overriding it replaces the default behavior (dismiss on close, open URLs),
// so keep those cases if you want to preserve it.
@override
void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) {
switch (action) {
case const CloseAction():
case const AndroidSystemBackAction(): // close the flow on the Android back button
view.dismiss();
break;
case OpenUrlAction(:final url, :final openIn):
AdaptyUI().openUrl(url, openIn: openIn);
break;
default:
break;
}
}
// Required: decide what happens after a purchase finishes
@override
void flowViewDidFinishPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) {
if (purchaseResult is! AdaptyPurchaseResultUserCancelled) {
view.dismiss();
}
}
// Required: dismiss the flow once a restore succeeds
@override
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
view.dismiss();
}
// Required: handle rendering and other view errors
@override
void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) {
print('Flow error: $error');
view.dismiss();
}
}
Registra el handler una sola vez al inicio de la app, antes de mostrar ningún flow:
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
Próximos pasos
¿Tienes preguntas o estás teniendo algún problema? Consulta nuestro foro de soporte 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 o en Google Play Store para asegurarte de que puedes completar una compra de prueba desde el paywall.
Ahora necesitas comprobar el nivel de acceso de los usuarios para asegurarte de que muestras un paywall o concedes acceso a las funciones de pago a los usuarios correctos.
Ejemplo completo
Aquí puedes ver cómo integrar todos esos pasos en tu app.
void main() {
// Register a single, long-lived observer once, before any flow is shown.
// It is intentionally a plain object (NOT a Widget/State): its lifetime is the
// whole app, so it never leaks and is never silently replaced when screens are
// pushed or popped.
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
runApp(MaterialApp(home: FlowScreen()));
}
/// A dedicated handler for AdaptyUI flow events.
///
/// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented
/// by a `State`), which gives you two things for free:
/// * the SDK's sensible defaults for optional callbacks, so besides the three
/// required methods you only override what you actually care about;
/// * a lifecycle that is independent of the widget tree — there is no strong
/// reference back into a `Widget`, so nothing leaks and there is nothing to
/// unregister.
///
/// Every callback receives the [AdaptyUIFlowView] it relates to, so handling
/// flow actions never requires a `BuildContext` or widget state.
class FlowEventsHandler extends AdaptyUIFlowsEventsObserver {
// A single, app-wide instance — same idiom as Adapty() and AdaptyUI().
static final FlowEventsHandler _instance = FlowEventsHandler._();
factory FlowEventsHandler() => _instance;
FlowEventsHandler._();
// Called when the user performs an action on the flow UI.
@override
void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) {
switch (action) {
case const CloseAction():
case const AndroidSystemBackAction(): // close the flow on the Android back button
view.dismiss();
break;
case OpenUrlAction(:final url, :final openIn):
// Open the URL natively, honoring the dashboard browser setting.
AdaptyUI().openUrl(url, openIn: openIn);
break;
default:
break;
}
}
// Required: decide what happens after a purchase finishes.
@override
void flowViewDidFinishPurchase(AdaptyUIFlowView view,
AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) {
if (purchaseResult is! AdaptyPurchaseResultUserCancelled) {
view.dismiss();
}
}
// Required: dismiss the flow once a restore succeeds.
@override
void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) {
view.dismiss();
}
// Required: handle rendering and other view errors.
@override
void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) {
print('Flow error: $error');
view.dismiss();
}
}
class FlowScreen extends StatefulWidget {
const FlowScreen({super.key});
@override
State<FlowScreen> createState() => _FlowScreenState();
}
class _FlowScreenState extends State<FlowScreen> {
@override
void initState() {
super.initState();
_showFlowIfNeeded();
}
Future<void> _showFlowIfNeeded() async {
try {
final flow = await Adapty().getFlow(
placementId: 'YOUR_PLACEMENT_ID',
);
if (!flow.hasViewConfiguration) return;
final view = await AdaptyUI().createFlowView(flow: flow);
await view.present();
} catch (_) {
// Handle any errors (network, SDK issues, etc.)
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Adapty Flow Example')),
body: Center(
// Add a button to re-trigger the flow for testing purposes.
child: ElevatedButton(
onPressed: _showFlowIfNeeded,
child: const Text('Show Flow'),
),
),
);
}
}