Docs Install
React Native and Expo apps
Monitor a React Native or Expo app with one vendored file: screens, cold starts, crashes, failed requests and optional native session replay.
A mobile app is measured by a single JavaScript file that you save into the app's repository. It is not loaded from a server at run time. Your app's JavaScript is compiled into a binary that goes through app review, so the agent has to travel with it.
Create a mobile application
Go to Launchpad → Install → Mobile application and press + New application, choosing that it is an app. A mobile app is a separate application from your website, with its own key and its own numbers. A crash-free rate and a largest contentful paint do not belong in one tile. The page only ever offers a mobile application's key. A website's key pasted into an app would mix two kinds of session that can never be separated again.
Add the file
curl -sS https://your-install/rn.js -o lib/obscenter.js
The file imports react-native and nothing else. You review it like your own code, and it needs no build step. The agent's version travels on every report, which is how you tell a released build that has a fix from one that does not.
Start it once, at the entry point
import * as obscenter from './lib/obscenter';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Constants from 'expo-constants';
obscenter.init({
key: 'rum_…',
url: 'https://your-install',
storage: AsyncStorage,
app: {
name: Constants.expoConfig?.name,
version: Constants.expoConfig?.version,
},
});
The collector's address is compiled into the app, so it is written out in full. storage is optional, but it is the one option worth passing. Without it the visitor id is new on every launch, and events queued while there was no signal are lost if the system kills the app. With it, up to 1000 queued events are saved when the app goes to the background and sent on the next launch.
Other options are sample (0 to 1), sessionTimeout (milliseconds, 30 minutes by default) and traceOrigins (up to 20 exact origins of your API that should receive traceparent). trackErrors, trackNetwork and trackLongTasks are on by default, and debug is off. A phone has no page origin to infer an API from, so name yours in traceOrigins.
Tell it which screen is on
import { usePathname } from 'expo-router';
import * as obscenter from '../lib/obscenter';
// in the root layout, once
const pathname = usePathname();
useEffect(() => { obscenter.screen(pathname); }, [pathname]);
This is the one thing the agent cannot do for itself. React Native dispatches navigation through your component tree, so there is nothing global to listen to. The rest of the API mirrors the browser tag: identify, event, action, error, attrs (values attached to the session) and flush.
What it measures
- Screens, session length, taps and identified users.
- Cold starts measured from the tap. React Native reports its own start marks, the first of them set by native code, so the cold start splits into native, runtime, bundle and your app's own work. A build that sets no marks falls back to the start of the JavaScript and leaves the breakdown blank rather than filling it in. A return after at least three seconds in the background counts as a warm start.
- JavaScript errors, with the app version on them. A fatal error counts as a crash.
- Every XHR and fetch call, including failed ones.
- Long tasks: how long the JavaScript thread was held. The agent asks
PerformanceObserver.supportedEntryTypesat start-up and watches long tasks only where the build emits them. - The handset model and OS version, declared by the runtime rather than guessed from a user agent.
What a phone cannot tell you
Core Web Vitals and the resource waterfall are not measured. LCP, INP and CLS are properties of a document being laid out, and an app has no document. Nothing reports a substitute under their name. A measurement the runtime cannot take is simply not sent, and the dashboard draws a gap.
Session replay (optional)
Replay needs a native capture module and two lines in your code. The module is a local Expo module. It cannot be downloaded from the install yet, so ask for it at info@obscenter.site. Then:
# unpack the module into ./modules/obscenter-replay, then: npx expo prebuild
import { source } from '../modules/obscenter-replay';
obscenter.attachReplaySource(source); // right after init()
// the root View, once: where fingers land, for the recording only
<View style={{ flex: 1 }}
onTouchStart={(e) => obscenter.pointer({ x: e.nativeEvent.pageX, y: e.nativeEvent.pageY })}>
It needs a development build, because Expo Go cannot load the module. It records nothing until the application's replay percentage is above zero. The application chooses between two modes:
- Screenshots (the default): the screen captured as H.264 video on the handset, in segments of about five seconds. The video holds whatever was on screen, and nothing in it is masked yet.
- Wireframe: rectangles where things were. Characters inside a text view are never read. In this mode the collector refuses any node that carries a string other than a colour, so even a build with a masking bug stores no text.
Both the mode and the percentage are read from the server, so you can change them without a release. Without the onTouchStart line you get the recording with no taps on it.
The mobile dashboard
A mobile application's Overview shows app sessions, crash-free sessions, cold start p75, screens per session, session length and identified users. Crash-free is shown as good from 99.5%. With no sessions it shows nothing, rather than a reassuring 100%. Launches split by platform, by build, by OS version and by handset. A build is a fleet rather than a deployment, so every table compares versions.
A mobile application has no Pages, Funnels or Resources, because a screen is not a page. User sessions, the world map, errors and API calls work as they do for a website. If a website starts receiving native sessions, it gains the mobile section beside its own.