Get from zero Adapty setup to displaying a paywall and processing purchases in under 60 minutes using Expo and React Native.
Prerequisites
- You have a working React Native app (built with Expo)
- You’re using EAS Build and Dev Client (not Expo Go)
- Your app includes at least one in-app purchase created in App Store Connect and synced in the Adapty dashboard
- You’ve created an Adapty account and your app is added in the Adapty dashboard
Project Files
To follow along with the written, video, or LLM-based tutorial, download the project files from Github. The branch named starter
contains the app without any Adapty integration. The main
branch contains the finished project.
Install the Adapty SDK
Install the Adapty SDK and required helper:
npx expo install react-native-adapty tslib
tslib
is a runtime dependency of the Adapty SDK.
Pre-build your Expo project
Because Adapty includes native code, you need to generate native iOS/Android directories:
npx expo prebuild
This will generate an ios
and android
folder, and a Podfile
.
If you already have a prebuilt project, you can skip this step.
Add Adapty to your iOS project
Open the Podfile
inside your ios/
directory and confirm Adapty was added. Then run:
cd ios
pod install
cd ..
You’ll also need to build a custom development client to run the app locally:
eas build --profile development --platform ios
Once built and installed, run the app using:
npx expo start --dev-client
Activate the SDK on app launch
Create AdaptyConstants.js
to store keys and values you’ll use throughout the project:
export default {
API_KEY: "your-public-sdk-key",
ACCESS_LEVEL_ID: "premium",
PLACEMENT_ID: "on_tap_history",
};
Create a new file called AdaptyService.js
in your root directory and add:
import { adapty } from "react-native-adapty";
import AdaptyConstants from "./AdaptyConstants";
export const activationPromise = (async () => {
try {
await adapty.activate(AdaptyConstants.API_KEY);
} catch (error) {
console.error("Adapty activation failed:", error);
}
})();
This exports a shared activation promise that will be awaited anywhere the SDK is used. It configures and activates the Adapty SDK. The Adapty UI library is built in, allowing you to display remotely configured paywalls with made with Paywall Builder.
Load the user’s subscription status
It’s important to have up-to-date information on the user’s subscription status. Adapty makes this easy with something called a user profile.
In ProfileContext.js
, modify your context to include refreshing this profile. Here’s a version showing just the additions:
// Add the following imports
import { adapty } from "react-native-adapty";
import AdaptyConstants from "../AdaptyConstants";
import { activationPromise } from "../AdaptyService";
...
export function ProfileProvider({ children }) {
...
// Add this useEffect() to get a fresh profile from Adapty
useEffect(() => {
let mounted = true;
(async () => {
try {
await activationPromise;
const profile = await adapty.getProfile();
if (profile && profile.accessLevels) {
const level = profile.accessLevels[AdaptyConstants.ACCESS_LEVEL_ID];
if (mounted) {
setIsPremium(
!!(level?.isActive || level?.isInGracePeriod || level?.isLifetime)
);
}
}
} catch (error) {
console.error("Error fetching Adapty profile:", error);
}
})();
return () => {
mounted = false;
};
}, []);
...
}
This code fetches an updated AdaptyProfile
object from the SDK with the latest subscription and access level information. It then checks the access level and updates isPremium
accordingly. You can use this within the app to determine whether to show premium content or functionality.
Show the Paywall
The next step is to present a paywall from Paywall Builder where applicable. In HomeScreen.js
, when the user taps “View History”, we want to either navigate to the history screen (if they’re premium) or show a paywall using Adapty.
In the Home Screen, add these imports:
// Add these imports
import { adapty } from "react-native-adapty";
import { createPaywallView } from "react-native-adapty/dist/ui";
import AdaptyConstants from "../AdaptyConstants";
import { activationPromise } from "../AdaptyService";
Before we can show the Paywall Builder paywall, we need to fetch it from Adapty’s servers. We’ll do that and then create and present a paywall view based on that configuration.
export default function HomeScreen({ navigation }) {
...
// Modify handleViewHistory to look like this:
async function handleViewHistory() {
await activationPromise;
if (isPremium) {
navigation.navigate("History");
} else {
try {
const paywall = await adapty.getPaywall(
AdaptyConstants.PLACEMENT_ID,
"en"
);
if (paywall.hasViewConfiguration) {
const view = await createPaywallView(paywall);
view.registerEventHandlers({
onCloseButtonPress() {
return true;
},
onPurchaseCompleted(purchaseResult, product) {
if (purchaseResult.type === "success") {
purchasePremium(purchaseResult.profile);
navigation.navigate("History");
return true;
}
return false;
},
onPurchaseFailed(error) {
console.error("Purchase failed:", error);
return false;
},
onRestoreCompleted(restoredProfile) {
purchasePremium(restoredProfile);
navigation.navigate("History");
return true;
},
onRestoreFailed(error) {
console.error("Restore failed:", error);
return false;
},
onRenderingFailed(error) {
console.error("Error rendering paywall:", error);
return false;
},
});
await view.present();
}
} catch (error) {
console.error("Error loading paywall:", error);
}
}
}
...
}
The above block contains everything we need to display and manage the purchase process for a paywall. The various closures are used when success and failure states are triggered. They are your opportunity to take over control of the user experience and respond according to the outcome. The most common outcome is onPurchaseCompleted
. In that case, we update the profile in our profile manager, and unlock premium content.
Define and manage your Access Levels in the Adapty Dashboard > Monetization > Access Levels
Congrats!
At this point, your app:
- Initializes the Adapty SDK on launch
- Loads a paywall dynamically from the Adapty dashboard
- Supports purchasing and restoring subscriptions
- Unlocks premium content using
isPremium
from your context