# React Native & Expo

> Track screens, events and signed-in users in React Native and Expo apps, with an offline queue and consent.

Source: https://metricraven.com/docs/react-native  
Markdown: https://metricraven.com/docs/react-native.md  
Section: Installation guides

`@metricraven/react-native` records screen views, goals and identified users from your app. Events wait in a queue while the device is offline and are sent when it reconnects.

## Set up

1. **Allow your app in Settings**

   In your site's **Settings**, add your bundle ID (iOS) or package name (Android) under **Mobile apps**, for example `com.example.app`. App events from IDs that aren't listed are rejected.

2. **Install the SDK**

   AsyncStorage is optional, but without it queued events are lost if the app is closed while offline.

   ```bash
   npx expo install @metricraven/react-native @react-native-async-storage/async-storage
   ```

3. **Create the client once**

   ```typescript title="analytics.ts"
   // analytics.ts
   import AsyncStorage from "@react-native-async-storage/async-storage";
   import * as Application from "expo-application";
   import { createMetricRaven } from "@metricraven/react-native";

   export const analytics = createMetricRaven({
     siteId: process.env.EXPO_PUBLIC_METRICRAVEN_SITE_ID!,
     appId: Application.applicationId ?? "com.example.app", // listed under Settings → Mobile apps
     appVersion: Application.nativeApplicationVersion ?? undefined,
     appBuild: Application.nativeBuildVersion ?? undefined,
     storage: AsyncStorage, // keeps the offline queue (and, with consent, the visitor ID)
     disabled: __DEV__,
   });
   ```

4. **Track screens**

   With Expo Router, track the route pattern from your root layout:

   ```typescript title="app/_layout.tsx"
   // app/_layout.tsx
   import { Slot, useSegments } from "expo-router";
   import { MetricRavenProvider, useExpoRouterTracking } from "@metricraven/react-native";
   import { analytics } from "../analytics";

   function ScreenTracking() {
     useExpoRouterTracking(useSegments()); // "/users/[id]", never the real ID
     return null;
   }

   export default function RootLayout() {
     return (
       <MetricRavenProvider client={analytics}>
         <ScreenTracking />
         <Slot />
       </MetricRavenProvider>
     );
   }
   ```

   With React Navigation, pass the helpers to your container. It records route names, not params:

   ```typescript title="App.tsx"
   // App.tsx
   import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
   import { MetricRavenProvider, trackNavigation } from "@metricraven/react-native";
   import { analytics } from "./analytics";

   export default function App() {
     const navigationRef = useNavigationContainerRef();
     return (
       <MetricRavenProvider client={analytics}>
         <NavigationContainer ref={navigationRef} {...trackNavigation(analytics, navigationRef)}>
           {/* your navigators */}
         </NavigationContainer>
       </MetricRavenProvider>
     );
   }
   ```

5. **Send events and identify users**

   ```typescript
   analytics.track("cta_click", { source: "home" });
   analytics.screen("Paywall"); // a screen view outside your navigator
   analytics.identify(user.id, { name: user.name, email: user.email, plan: "pro" });
   analytics.reset(); // on logout
   analytics.setConsent(true); // from your consent prompt
   analytics.openedFromUrl(url); // utm_* from a deep link, for the next screen view

   // In components: const analytics = useMetricRaven(); useScreen("Settings");
   ```

> **Verify it works**
>
> Open the app on a device or simulator with `disabled` off. The screen appears in **Realtime**. Screens are listed under **Pages** as paths like `/Home` or `/users/[id]`, and your app ID appears under **Hostnames**.

## Client

| Call | Notes |
| --- | --- |
| screen(name, props?) | A screen view. Names like "Settings" become the path /Settings. |
| track(name, props?) | A custom goal, attributed to the current screen. Up to 10 properties. |
| identify(userId, traits?) | Links this device's visitor to your user ID, with optional name, email and up to 10 custom traits. |
| reset() | On logout. Forgets identify state and, with consent, starts a new visitor ID. |
| setConsent(granted) | Grants or withdraws storage consent. Withdrawing also removes the ID from queued events. |
| openedFromUrl(url) | Reads utm_\* parameters from a deep link and attaches them to the next screen view. |
| flush() | Sends the queue now. Runs automatically when the app returns to the foreground or goes to the background. |
| getVisitorId() / onVisitorId() | The current visitor ID, and a listener for changes. |

| Option | What it does |
| --- | --- |
| siteId, appId | Required. appId must be listed under Mobile apps. |
| appVersion, appBuild | Shown on events and visitor profiles. Read them from expo-application or react-native-device-info. |
| storage | AsyncStorage. Persists the queue and, with consent, the visitor ID. |
| consent | Set true when the user has already allowed analytics storage. |
| apiHost / endpoint | Where events go. Defaults to https://metricraven.com/api/v1/collect. |
| maxQueueSize, maxEventAgeMs | Defaults to 500 events and 7 days. Older or extra events are dropped. |
| disabled | Turns tracking off, for example in development. |

## Consent and visitor IDs

The app follows the same rules as the website tracker. Until you call `setConsent(true)` (or pass `consent: true`), nothing that identifies the device is stored. The server derives a daily visitor ID from the network address and the app's user agent. Raw IP addresses are not kept. With consent, a random ID is kept in `storage`, so sessions on different days connect. Granting consent also relabels today's events from that device.

> **Note:** Mobile networks change address often, so without consent one person can appear as several visitors in a day. Ask for consent if returning-user numbers matter to you.

## Offline queue and retries

Events are sent one at a time, in order. Network errors, timeouts, rate limits and server errors are retried with growing waits, up to 5 minutes, and `Retry-After` is respected. Rejected events are dropped instead of retried. That covers invalid data, an app ID that isn't listed, an unknown site, or paused tracking. Events replayed more than 5 minutes after they happened are recorded at the time they arrive.

## Details

- App traffic is identified by your app ID because native requests have no web page or browser origin. A browser can't use this to send data as your app: browser requests always carry an origin, and those are checked against your domains.
- Like the website tag, the site ID and app ID aren't secrets. The allowlist keeps out misconfigured and casual traffic, not a determined forger. For verified events, use [server events](https://metricraven.com/docs/server-events) or [server identify](https://metricraven.com/docs/identify-users).
- Expo Router: use `useSegments()` rather than `usePathname()`, so IDs in URLs (like `/users/42`) don't show up in reports.
- Purchases inside the app: see [RevenueCat](https://metricraven.com/docs/revenuecat) and send the same user ID you pass to `identify`.
- `@metricraven/react-native/core` exports the client without React or react-native imports, for tests and other JavaScript runtimes.
