> For the complete documentation index, see [llms.txt](https://docs.amply.tools/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amply.tools/developer-guide/react-native-integration.md).

# React Native integration

In plain English: this page is a collection of how-tos for the Amply React Native SDK beyond "just get it running." It covers peer dependencies, the iOS pod install step, Android autolinking, using the `useAmplySystemEvents` hook, cleaning up listeners so you don't leak memory, and handling re-initialization when the JS bundle reloads. A PM can skim the headings; an engineer copies the TypeScript snippets.

**Use this when** the React Native SDK is installed and you need to wire a specific flow. **Don't use this when** you haven't finished the Quickstart yet — start there.

## Before you start

* React Native 0.79+, Expo SDK 54+ (or bare RN 0.79+) — see [Installation](/developer-guide/installation.md) for the full requirements matrix.
* New Architecture enabled (the SDK uses the new native module system).
* Node 18+.

## Peer dependencies and install

The SDK requires `react >=18.2.0`, `react-native >=0.79.0`, and `expo >=54.0.0` as peer dependencies — make sure your app has them, then install:

```bash
npm install @amplytools/react-native-amply-sdk
```

## iOS: run pod install

After installing the npm package, refresh the iOS native project. This links the native iOS SDK into your app.

```bash
cd ios
pod install
cd ..
```

Run this every time you change the installed npm version of the SDK.

## Android: autolinking is automatic

On Android there is no manual Gradle edit. The SDK ships a `react-native.config.js` entry that React Native's autolinking picks up on the next build. Just rebuild:

```bash
npx react-native run-android
```

If you use Expo with `expo prebuild`, the SDK's config plugin registers the native package for you during prebuild.

## Initialize once, early

Call `initialize` once, as close to app start as possible. The function returns a Promise that resolves when the native SDK is ready.

**Signature:**

```ts
Amply.initialize(config: AmplyInitializationConfig): Promise<void>

type AmplyInitializationConfig = {
  appId: string;
  apiKeyPublic: string;
  apiKeySecret?: string | null;
  endpoint?: string | null;
  defaultConfig?: string | null;
  debug?: boolean | null;
  logLevel?: LogLevel | null;
};
```

```tsx
// App.tsx
import {useEffect} from 'react';
import Amply from '@amplytools/react-native-amply-sdk';

export default function App() {
  useEffect(() => {
    Amply.initialize({
      appId: 'com.example.app',
      apiKeyPublic: 'pk_...',
      apiKeySecret: 'sk_...',
      logLevel: 'warn',
    }).catch(err => console.warn('[Amply] init failed', err));
  }, []);

  return <RootNavigator />;
}
```

`Amply.isInitialized()` returns `true` synchronously once `initialize` has resolved.

### Custom property value types

RN custom properties accept `string | number | boolean` only. The iOS and Android native SDKs accept more types — stick to primitives on React Native.

## Re-init on Fast Refresh / JS reload

Pressing R in Metro re-runs your JS bundle but the native side stays alive. The native SDK remains initialized across JS reloads. Guarding your init is optional but keeps logs clean:

```ts
if (!Amply.isInitialized()) await Amply.initialize(config);
```

On a cold launch, both JS and native start fresh — always call `initialize` on mount.

## Listening to system events

System events are SDK lifecycle signals: config loaded, session started, campaign evaluated. Two APIs are available.

### Option 1: the hook (good for debug UI)

**Signature:**

```ts
useAmplySystemEvents(options?: {
  maxEntries?: number;       // default 50
  dedupe?: boolean;          // default true
  onEvent?: (event: EventRecord) => void;
}): {events: EventRecord[]; reset: () => void};
```

```tsx
import {useAmplySystemEvents} from '@amplytools/react-native-amply-sdk';

function DebugPanel() {
  const {events, reset} = useAmplySystemEvents({maxEntries: 20});
  return (
    <View>
      {events.map(e => <Text key={e.id}>{e.name} @ {e.timestamp}</Text>)}
      <Button title="Clear" onPress={reset} />
    </View>
  );
}
```

### Option 2: the raw listener (good for production)

Returns a Promise that resolves to an unsubscribe function.

**Signature:**

```ts
Amply.addSystemEventListener(
  listener: (event: EventRecord) => void
): Promise<() => void>;
// Alias: Amply.systemEvents.addListener(listener)
```

```tsx
useEffect(() => {
  let unsubscribe: (() => void) | undefined;
  let cancelled = false;

  Amply.systemEvents.addListener(event => {
    console.log('[Amply]', event.name, event.properties);
  }).then(unsub => {
    if (cancelled) unsub();           // component unmounted before subscribe landed
    else unsubscribe = unsub;
  });

  return () => {
    cancelled = true;
    unsubscribe?.();                  // always call the unsubscribe
  };
}, []);
```

{% hint style="warning" %}
Always call the returned unsubscribe in cleanup. Subscriptions that outlive the component that created them will keep the listener running on stale state and can leak memory across navigations.
{% endhint %}

## Receiving campaign deeplinks

Same Promise-returns-unsubscribe shape.

**Signature:**

```ts
Amply.addDeepLinkListener(
  listener: (event: DeepLinkEvent) => void
): Promise<() => void>;

type DeepLinkEvent = {url: string; info: JsonMap; consumed: boolean};
```

```tsx
useEffect(() => {
  let unsubscribe: (() => void) | undefined;
  let cancelled = false;
  Amply.addDeepLinkListener(event => {
    navigation.navigate('Route', {url: event.url});
  }).then(unsub => { if (cancelled) unsub(); else unsubscribe = unsub; });
  return () => { cancelled = true; unsubscribe?.(); };
}, [navigation]);
```

Declare your URL scheme in `Info.plist` and `AndroidManifest.xml` — see [iOS integration](/developer-guide/ios-integration.md#registering-your-deeplink-scheme) and [Android integration](/developer-guide/android-integration.md#registering-deeplink-intent-filters).

## Tracking events and identifying the user

```ts
Amply.track({name: 'ScreenViewed', properties: {screen: 'Home'}});

Amply.setUserId('user_42');
Amply.setCustomProperties({plan: 'premium', seats: 3, trial: false});
await Amply.getCustomProperty('plan');   // 'premium'
Amply.setUserId(null);                   // clear
```

See [Tracking events](/developer-guide/tracking-events.md) and [User attributes](/developer-guide/user-attributes.md) for payload rules.

## Tearing down all listeners

For hard resets (user logs out, tests):

```ts
Amply.removeAllListeners();
```

This clears every deeplink subscription the SDK is currently tracking. It does not uninitialize the SDK.

## Log level

```ts
Amply.setLogLevel('debug');   // 'none' | 'error' | 'warn' | 'info' | 'debug'
Amply.getLogLevel();
```

Debug logging is forwarded to `console` automatically when the level is not `'none'`.

## Related

* [React Native quickstart](/developer-guide/quickstart-react-native.md) — initial setup, not covered here.
* [Tracking events](/developer-guide/tracking-events.md) — payload shapes and common patterns.
* [Handling deeplinks](/developer-guide/handling-deeplinks.md) — cross-platform deeplink routing patterns.
* [iOS integration](/developer-guide/ios-integration.md) — platform-specific flows that still apply under RN.
* [Android integration](/developer-guide/android-integration.md) — platform-specific flows that still apply under RN.
* [SDK reference: React Native](/reference/sdk-react-native.md) — full method list.
