React Native
Install and wire the Rovenue SDK in an Expo or bare React Native app.
In an Expo app, use the React Native version your Expo SDK pins — run
npx expo install --check (or --fix) after upgrading either. Expo ships
ExpoModulesCore prebuilt against its pinned React Native's JSI ABI (Expo 56
pins React Native 0.85.3); a newer React Native changes that ABI, and the app
dies at launch in dyld with Symbol not found: …jsi… before any JavaScript or
Rovenue code runs. examples/sample-rn-expo (Expo 56, React Native 0.85.3)
runs on the iOS simulator in this monorepo.
Install the package
Install with your package manager (your choice is remembered across the whole site):
npm install @rovenue/react-native-sdkExpo managed workflow — add the config plugin to app.json (or app.config.js). It auto-wires the iOS CocoaPods dependency and Android Gradle on prebuild:
{
"expo": {
"plugins": ["@rovenue/react-native-sdk"]
}
}Then run:
npx expo prebuildBare React Native — after installing, run pod install for iOS:
cd ios && pod installSupported platforms: iOS 16.0+ (device and simulator).
Peer dependencies: expo >=52, expo-modules-core >=2, react >=18.3, react-native >=0.76 — and, within those, the React Native version your Expo SDK pins.
Configure the SDK
Call Rovenue.configure once at the root of your app — before any hooks are used. App.tsx is the right place:
import { Rovenue } from '@rovenue/react-native-sdk';
Rovenue.configure({
apiKey: 'rov_pub_YOUR_API_KEY',
logLevel: 'info',
});
export default function App() {
// Foreground wiring added in the next step
return <RootNavigator />;
}Self-hosting? baseUrl is optional and defaults to the hosted endpoint https://api.rovenue.io. Override it to point at your own deployment, e.g. Rovenue.configure({ apiKey: 'rov_pub_YOUR_API_KEY', baseUrl: 'https://api.acme.com' }). The host must use https:// — plain http:// is accepted only for localhost during local development.
Types on this page
type RovenueConfig = {
apiKey: string; // public SDK key — prefix rov_pub_
baseUrl?: string; // optional — defaults to https://api.rovenue.io
logLevel?: 'off' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; // default 'warn'
appVersion?: string; // override auto-detected version
environment?: 'prod' | 'staging' | 'development';
};| Field | Type | Description |
|---|---|---|
apiKey | string | Public SDK key from your Rovenue dashboard. Prefix rov_pub_. |
baseUrl | string? | Optional. Defaults to https://api.rovenue.io. Override only when self-hosting (https://; http:// allowed for localhost only). |
logLevel | string? | Verbosity of SDK logs. Defaults to 'warn' — turn up to 'info'/'debug' locally, never ship 'debug'/'trace' in production. |
appVersion | string | Overrides the version auto-read from the bundle. |
environment | string? | Optional environment label forwarded with requests. |
Call configure exactly once, before rendering any component that calls a Rovenue hook. Calling it more than once after initialisation is a no-op but logs a warning.
Wire foreground signals
The SDK uses setForeground to manage its internal polling cadence. Wire it to React Native's AppState inside your root component:
import { useEffect } from 'react';
import { AppState } from 'react-native';
import { Rovenue } from '@rovenue/react-native-sdk';
export default function App() {
useEffect(() => {
// Signal foreground on mount
Rovenue.setForeground(true);
const sub = AppState.addEventListener('change', (state) => {
Rovenue.setForeground(state === 'active');
});
return () => {
sub.remove();
};
}, []);
return <RootNavigator />;
}Rovenue.shutdown() stops all background work (polling, in-flight refreshes). Reserve it for full app teardown — do not call it on every background transition. setForeground(false) already pauses polling when the app backgrounds.
Identifying users
Anonymous subscribers get a permanent rovenueId automatically. Call identify once your own auth flow completes, to link that install to your backend user:
import { Rovenue } from '@rovenue/react-native-sdk';
// After your auth flow completes:
await Rovenue.identify('user_123');
// When your user signs out:
await Rovenue.logOut();useCurrentUser() (below) reflects the result reactively — no manual refetch needed.
See Identifying Users for the full identity model, including what happens when identify and logOut are called.
React hooks
All hooks must be called from components rendered after configure has been called.
| Hook | Returns |
|---|---|
useCurrentUser() | User | null — the identified subscriber, or null |
useEntitlement(id) | Entitlement | null — entitlement object for the given ID |
useEntitlements() | Entitlement[] — all active entitlements |
useVirtualCurrencies() | Record<string, number> — all virtual-currency balances |
useVirtualCurrency(code) | number — balance for a single currency code (0 if absent) |
type User = {
rovenueId: string; // permanent per-install id (prefix rov_), always present
appUserId: string | null; // your backend user id, null until identify()
};
type Entitlement = {
id: string; // e.g. "pro", "credits_500"
isActive: boolean;
productIdentifier: string;
store: string; // "app_store" | "play_store" | "stripe"
expiresIso?: string; // ISO 8601 — present for time-limited entitlements
};| Field | Type | Description |
|---|---|---|
rovenueId | string | Permanent per-install id (prefix rov_); never changes. |
appUserId | string | null | Your backend user id; null until identify(). |
id | string | Entitlement identifier configured in your dashboard. |
isActive | boolean | true if the entitlement is currently active. |
expiresIso | string | ISO 8601 expiry; present for time-limited entitlements. |
Example — gating on an entitlement
import { useEntitlement } from '@rovenue/react-native-sdk';
export function ProBadge() {
const pro = useEntitlement('pro');
if (!pro?.isActive) {
return null;
}
return <Text>Pro member</Text>;
}Example — displaying virtual-currency balances
import { useVirtualCurrency, useVirtualCurrencies } from '@rovenue/react-native-sdk';
// Show one currency by code
export function CoinsLabel() {
const coins = useVirtualCurrency('COINS');
return <Text>{coins} coins</Text>;
}
// Show all currencies
export function WalletSummary() {
const balances = useVirtualCurrencies(); // Record<string, number>
return (
<>
{Object.entries(balances).map(([code, amount]) => (
<Text key={code}>{code}: {amount}</Text>
))}
</>
);
}See Entitlements and Virtual Currencies for the full guide on reading and reacting to subscription state.
Purchases
Use getOfferings() to fetch products configured in your Rovenue dashboard, then purchase(pkg) to drive the full purchase flow. The SDK triggers the native StoreKit 2 / Play Billing sheet, validates the receipt server-side, and resolves with updated entitlements and virtual-currency balances. On Android the SDK acknowledges or consumes the Play purchase on the device after the server verified it, and purchase resolves even when Play cannot take that acknowledgement yet — see Who acknowledges a Play purchase.
import { Rovenue, RovenueError } from '@rovenue/react-native-sdk';
const offerings = await Rovenue.getOfferings();
const pkg = offerings.current?.packages[0];
if (pkg) {
try {
const result = await Rovenue.purchase(pkg);
if (result.isDeferred) {
// Ask-to-Buy / parental-control hold — no entitlements yet. The SDK
// reconciles it in the background; watch addChangeListener for
// 'ENTITLEMENTS_CHANGED' to know when it resolves.
} else {
// result.entitlements and result.virtualCurrencies are already up to date
}
} catch (e) {
if (e instanceof RovenueError && e.kind === 'PurchaseCanceled') {
// User dismissed the store sheet — no action needed
} else {
throw e;
}
}
}To re-validate all prior transactions (e.g. after a reinstall):
await Rovenue.restorePurchases();type PurchaseResult = {
entitlements: Entitlement[];
virtualCurrencies: Record<string, number>;
productId: string;
storeTransactionId: string;
isDeferred: boolean; // true for an Ask-to-Buy / parental-control hold
};| Field | Type | Description |
|---|---|---|
entitlements | Entitlement[] | Updated entitlement list after the purchase. |
virtualCurrencies | Record<string, number> | Updated virtual-currency balances after the purchase (currency code → amount). |
productId | string | The product identifier that was purchased. |
storeTransactionId | string | The store-side transaction ID. |
isDeferred | boolean | true when the purchase is pending external approval — no entitlements are granted yet. |
There is no PurchaseCancelledError/PurchasePendingError — every failure is a single RovenueError class discriminated by .kind, and there is no 'PurchasePending' kind at all: as of 0.16.0, a deferred purchase does not throw; it returns normally with isDeferred = true. See Reference: Errors for the full 24-value ErrorKind taxonomy and Processing Purchases for the complete purchase API and the StoreProduct shape.
Funnels
Placements serve native funnels, drawn by RovenueFunnelView (see
Placements and
Show a Funnel). There is deliberately
no JS getFunnel — the native side resolves the placement itself:
import { RovenueFunnelView } from '@rovenue/react-native-sdk';
const onboarding = <RovenueFunnelView placementIdentifier="onboarding_end" onFinished={() => {}} />;Do not wrap RovenueFunnelView in a SafeAreaView: it keeps its content
clear of the system bars itself, and a wrapper stops the funnel's background
from reaching under them.
iOS: declare the languages your app supports
On iOS the funnel draws in the device's language only if your app declares
that language: iOS hands an app Locale.current from its declared
localizations, so an app that declares only English reaches the SDK as
en_XX (en_TR on a Turkish device) and shows the funnel's English copy.
With Expo, list them in app.json — "ios": { "infoPlist": { "CFBundleLocalizations": ["en", "tr"] } } — and prebuild; in a bare app,
add CFBundleLocalizations to ios/<App>/Info.plist. Android needs nothing.
Purchase attribution is automatic: the funnel's presentedContext is
attached to the next purchase for you.
Change listener
Use Rovenue.addChangeListener to react to server-pushed entitlement or virtual-currency updates. It returns an unsubscribe function — call it on cleanup.
import { useEffect } from 'react';
import { Rovenue } from '@rovenue/react-native-sdk';
useEffect(() => {
const unsub = Rovenue.addChangeListener((event) => {
// event: 'ENTITLEMENTS_CHANGED' | 'IDENTITY_CHANGED' | 'VIRTUAL_CURRENCIES_CHANGED' | 'REMOTE_CONFIG_CHANGED'
console.log('Rovenue change:', event);
});
return () => {
unsub();
};
}, []);See Reacting to Changes for a complete guide on handling live subscription events.
Funnels
The funnel widget hosts the platform's native renderer — there is no JS
getFunnel (packages/sdk-rn/src/index.ts:150-152) — full path: Show a funnel.
import { RovenueFunnelView } from "@rovenue/react-native-sdk";
function OnboardingFunnel() {
return <RovenueFunnelView placementIdentifier="onboarding" />;
}What's next
- Identifying Users — link your backend user to the Rovenue subscriber.
- Entitlements — read and gate on active subscriptions.
- Virtual Currencies — read virtual-currency balances and grant/spend via server-side APIs.
- Processing Purchases — full purchase API and error handling.
- App Lifecycle — foreground/background wiring in depth.
- Reference: Methods — full method reference.
- Reference: Types — all SDK types in one place.
- SDK API Reference — the generated TypeDoc reference for every exported type and method, at
/api/sdk-rn/.