Rovenue SDK
Platforms

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-sdk

Expo 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 prebuild

Bare React Native — after installing, run pod install for iOS:

cd ios && pod install

Supported 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';
};
FieldTypeDescription
apiKeystringPublic SDK key from your Rovenue dashboard. Prefix rov_pub_.
baseUrlstring?Optional. Defaults to https://api.rovenue.io. Override only when self-hosting (https://; http:// allowed for localhost only).
logLevelstring?Verbosity of SDK logs. Defaults to 'warn' — turn up to 'info'/'debug' locally, never ship 'debug'/'trace' in production.
appVersionstringOverrides the version auto-read from the bundle.
environmentstring?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.

HookReturns
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
};
FieldTypeDescription
rovenueIdstringPermanent per-install id (prefix rov_); never changes.
appUserIdstring | nullYour backend user id; null until identify().
idstringEntitlement identifier configured in your dashboard.
isActivebooleantrue if the entitlement is currently active.
expiresIsostringISO 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
};
FieldTypeDescription
entitlementsEntitlement[]Updated entitlement list after the purchase.
virtualCurrenciesRecord<string, number>Updated virtual-currency balances after the purchase (currency code → amount).
productIdstringThe product identifier that was purchased.
storeTransactionIdstringThe store-side transaction ID.
isDeferredbooleantrue 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

On this page