---
title: "Habilitar compras con Flow Builder en el SDK de Flutter"
description: "Guía de inicio rápido para habilitar compras in-app con Adapty Flow Builder."
---

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)
- [**Flows**](adapty-flow-builder) – secuencias de pantallas que presentan productos a los usuarios, creadas en el Flow Builder sin código. El SDK los recupera mediante `getFlow`. Si prefieres crear la UI en tu propio código, usa un paywall — consulta [Implementar paywalls manualmente](flutter-quickstart-manual).
- [**Placements**](placements) – dónde y cuándo mostrar flows en tu app (por ejemplo, `main`, `onboarding`, `settings`). Asocias flows a placements en el dashboard y luego los solicitas por ID de placement en tu código. Esto facilita ejecutar pruebas A/B y mostrar distintos flows a diferentes 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 usarlo                                                                                                                                                                                                                                |
|------------------------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Adapty Flow Builder | ✅ Fácil     | [Creas un flow completo y listo para compras en el constructor sin código](quickstart-paywalls). Adapty lo renderiza automáticamente y gestiona todo el flujo de compra, la validación de recibos y la gestión de suscripciones entre bastidores. |
| Paywalls creados manualmente | 🟡 Medio  | Implementas la UI de tu paywall en el código de tu app, pero sigues obteniendo el objeto flow desde Adapty para mantener flexibilidad en la oferta 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 flow creado en el Adapty Flow Builder.**

Si prefieres construir la UI del paywall tú mismo, consulta [Implementar paywalls manualmente](flutter-quickstart-manual).
:::

Para mostrar un flow creado en el Adapty Flow Builder, en el código de tu app solo necesitas:

1. **Obtener el flow**: Obténlo desde Adapty.
2. **Mostrarlo y Adapty gestionará las compras por ti**: Muestra la vista en tu app.
3. **Gestionar las acciones de los botones**: Asocia las interacciones del usuario con la respuesta de tu app. Por ejemplo, abrir enlaces o cerrar el flow 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 flow y añade productos](create-paywall).
4. [Crea un placement y añade tu flow](create-placement).
5. [Instala y activa el SDK](sdk-installation-flutter) en el código de tu app. Esta guía usa las APIs del SDK de Adapty Flutter v4.
:::tip
La forma más rápida de completar estos pasos es seguir la [guía de inicio rápido](quickstart) o crear paywalls y placements usando la [CLI para desarrolladores](developer-cli-quickstart).
:::
## 1. Obtener el flow \{#1-get-the-flow\}

Tus flows están asociados a placements configurados en el dashboard. Los placements te permiten ejecutar distintos flows para diferentes audiencias o realizar [pruebas A/B](ab-tests).

Para obtener un flow creado en el Adapty Flow Builder, necesitas:

1. Obtener el objeto `flow` por el ID del [placement](placements) usando el método `getFlow` y comprobar si fue creado en el builder mediante la propiedad `hasViewConfiguration`.

2. Crear la vista del flow usando el método `createFlowView`. La vista contiene los elementos de UI y el estilo necesarios para mostrar el flow.
:::important
Para obtener la configuración de la vista, debes activar el botón **Show on device** en el builder. De lo contrario, obtendrás una configuración de vista vacía y el flow no se mostrará.
:::

```dart showLineNumbers

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 \{#display-the-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`.

```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 flow, consulta nuestra [guía](flutter-present-paywalls).
:::
## 3. Gestionar las acciones de los botones \{#handle-button-actions\}

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.

:::tip
Lee nuestras guías sobre cómo manejar [acciones](flutter-handle-paywall-actions) y [eventos](flutter-handling-events) de botones.
:::
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.
```dart showLineNumbers title="Flutter"
// 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:

```dart showLineNumbers title="Flutter"
AdaptyUI().setFlowsEventsObserver(FlowEventsHandler());
```
## Próximos pasos \{#next-steps\}

:::tip
¿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\}

Aquí puedes ver cómo integrar todos esos pasos en tu app.
```dart

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'),
        ),
      ),
    );
  }
}
```