---
title: "Enable purchases with Flow Builder in React Native SDK"
description: "Quickstart guide to enabling in-app purchases with Adapty Flow Builder."
---

To enable in-app purchases, you need to understand three key concepts:

- [**Products**](product) – anything users can buy (subscriptions, consumables, lifetime access)
- [**Flows**](adapty-flow-builder) – screen sequences that present products to users, built in the no-code Flow Builder. The SDK retrieves them via `getFlow`. If you'd rather build the UI in your own code, use a paywall instead — see [Implement paywalls manually](react-native-quickstart-manual).
- [**Placements**](placements) – where and when you show flows in your app (like `main`, `onboarding`, `settings`). You attach flows to placements in the dashboard, then request them by placement ID in your code. This makes it easy to run A/B tests and show different flows to different users.

Adapty offers you three ways to enable purchases in your app. Select one of them depending on your app requirements:

| Implementation | Complexity | When to use |
|---|---|---|
| Adapty Flow Builder | ✅ Easy | You [create a complete, purchase-ready flow in the no-code builder](quickstart-paywalls). Adapty automatically renders it and handles all the complex purchase flow, receipt validation, and subscription management behind the scenes. |
| Manually created paywalls | 🟡 Medium | You implement your paywall UI in your app code, but still get the flow object from Adapty to maintain flexibility in product offerings. See the [guide](react-native-quickstart-manual). |
| Observer mode | 🔴 Hard | You already have your own purchase handling infrastructure and want to keep using it. Note that the observer mode has its limitations in Adapty. See the [article](observer-vs-full-mode). |

:::important
**The steps below show how to implement a flow created in the Adapty Flow Builder.**

If you'd rather build the paywall UI yourself, see [Implement paywalls manually](react-native-quickstart-manual).
:::

To display a flow created in the Adapty Flow Builder, in your app code, you only need to:

1. **Get the flow**: Get it from Adapty.
2. **Display it and Adapty will handle purchases for you**: Show the view in your app.
3. **Handle button actions**: Associate user interactions with your app's response to them. For example, open links or close the flow when users click buttons.

## Before you start

Before you start, complete these steps:

1. Connect your app to the [App Store](initial_ios) and/or [Google Play](initial-android) in the Adapty Dashboard.
2. [Create your products](create-product) in Adapty.
3. [Create a flow and add products to it](create-paywall).
4. [Create a placement and add your flow to it](create-placement).
5. [Install and activate the Adapty SDK](sdk-installation-reactnative) in your app code. This guide uses Adapty React Native SDK v4 APIs.

## 1. Get the flow

Your flows are associated with placements configured in the dashboard. Placements allow you to run different flows for different audiences or to run [A/B tests](ab-tests).

To get a flow created in the Adapty Flow Builder, get the `flow` object by the [placement](placements) ID using the `getFlow` method. The flow contains the UI elements and styling needed to display it.

```typescript showLineNumbers title="React Native"

try {
  const flow = await adapty.getFlow('YOUR_PLACEMENT_ID');
  // the requested flow
} catch (error) {
  // handle the error
}
```

## 2. Display the flow

Now, when you have the flow, it's enough to add a few lines to display it.

<Tabs groupId="presentation-method" queryString>
<TabItem value="platform" label="React component" default>

To embed a flow within your existing component tree, use the `AdaptyFlowView` component directly in your React Native component hierarchy:

```typescript showLineNumbers title="React Native (TSX)"

function MyFlow({ flow }) {
  const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>(
    (result, product) => result.type !== 'user_cancelled',
    [],
  );

  return (
    <AdaptyFlowView
      flow={flow}
      style={{ flex: 1 }}
      onPurchaseCompleted={onPurchaseCompleted}
    />
  );
}
```

</TabItem>
<TabItem value="standalone" label="Modal presentation">

To display the flow as a standalone screen, create a `view` with the `createFlowView` method, set its event handlers, then call `view.present()`. Each `view` can only be used once. If you need to display the flow again, call `createFlowView` one more time to create a new `view` instance.

```typescript showLineNumbers title="React Native"

try {
  const view = await createFlowView(flow);

  view.setEventHandlers({
    onPurchaseCompleted(result, product) {
      return result.type !== 'user_cancelled';
    },
  });

  await view.present();
} catch (error) {
  // handle the error
}
```

</TabItem>
</Tabs>

:::tip
For more details on how to display a flow, see our [guide](react-native-present-paywalls).
:::

## 3. Handle button actions

When users click buttons in the flow, the React Native SDK automatically handles purchases, restoration, closing the flow, and opening URLs.

However, other buttons have custom or pre-defined IDs and require handling actions in your code. Or, you may want to override their default behavior.

For example, here is the default behavior for the close button. You don't need to add it in the code, but here, you can see how it is done if needed.

<Tabs groupId="presentation-method" queryString>
<TabItem value="platform" label="React component" default>

For the React component, handle actions directly in the `AdaptyFlowView` component:

```typescript showLineNumbers title="React Native (TSX)"

function MyFlow({ flow }) {
  const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(
    () => true, // allow the flow to close
    [],
  );
  const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>(
    (actionId) => false,
    [],
  );

  return (
    <AdaptyFlowView
      flow={flow}
      style={{ flex: 1 }}
      onCloseButtonPress={onCloseButtonPress}
      onCustomAction={onCustomAction}
    />
  );
}
```

</TabItem>
<TabItem value="standalone" label="Modal presentation">

For modal presentation, implement event handlers using `setEventHandlers`:

```typescript showLineNumbers title="React Native"
const unsubscribe = view.setEventHandlers({
  onCloseButtonPress() {
    return true; // allow the flow to close
  },
});
```

</TabItem>
</Tabs>

:::tip
Read our guides on how to handle button [actions](react-native-handle-paywall-actions) and [events](react-native-handling-events-1).
:::

## Next steps

:::tip
Have questions or running into issues? Check out our [support forum](https://adapty.featurebase.app/) where you can find answers to common questions or ask your own. Our team and community are here to help!
:::

Your flow is ready to be displayed in the app. [Test your purchases](react-native-test) to make sure you can complete a test purchase from the flow.

Now, you need to [check the users' access level](react-native-check-subscription-status) to ensure you display a flow or give access to paid features to the right users.

## Full example

Here is how all the steps from this guide can be integrated in your app together.

<Tabs groupId="presentation-method" queryString>
<TabItem value="platform" label="React component" default>

```javascript showLineNumbers title="React Native (TSX)"

export default function FlowScreen() {
  const [flow, setFlow] = useState(null);

  const loadFlow = async () => {
    try {
      const flowData = await adapty.getFlow('YOUR_PLACEMENT_ID');
      setFlow(flowData);
    } catch (error) {
      console.warn('Error loading flow:', error);
    }
  };

  const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(
    () => true,
    [],
  );

  const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>(
    (result, product) => result.type !== 'user_cancelled',
    [],
  );

  useEffect(() => {
    loadFlow();
  }, []);

  return (
    <View style={{ flex: 1 }}>
      {flow ? (
        <AdaptyFlowView
          flow={flow}
          style={{ flex: 1 }}
          onCloseButtonPress={onCloseButtonPress}
          onPurchaseCompleted={onPurchaseCompleted}
        />
      ) : (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Button title="Load Flow" onPress={loadFlow} />
        </View>
      )}
    </View>
  );
}
```

</TabItem>
<TabItem value="standalone" label="Modal presentation">

```javascript showLineNumbers title="React Native"

export default function FlowScreen() {
  const showFlow = async () => {
    try {
      const flow = await adapty.getFlow('YOUR_PLACEMENT_ID');

      const view = await createFlowView(flow);

      view.setEventHandlers({
        onCloseButtonPress() {
          return true;
        },
        onPurchaseCompleted(result, product) {
          return result.type !== 'user_cancelled';
        },
      });

      await view.present();
    } catch (error) {
      // handle any error that may occur during the process
      console.warn('Error showing flow:', error);
    }
  };

  // you can add a button to manually trigger the flow for testing purposes
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Show Flow" onPress={showFlow} />
    </View>
  );
}
```

</TabItem>
</Tabs>