Connect a new app
- appId — the exact app slug, for example my-cool-app
- apiKey — the API key, which starts with hk_ (required in production)
- apiUrl — the hosted dashboard URL: https://saraayab.shop
The app-specific key is included in the mobile build and can be extracted. Treat it as public; never use your dashboard admin password in the app.
File paths are relative to your project root. Use the matching files in your app's folder structure.
Quick path — Expo Go, no Firebase
Install the published SDK + Expo dependencies
Open a terminal at your project root, then run:
npm install tester-analytics-sdk npx expo install expo-secure-store expo-constants
your project/package.jsonexpo-secure-store securely stores the persistent Tester ID, expo-constants lets the SDK read app.json. Both work in Expo Go.
Set the config in app.json
your project/app.json{
"expo": {
"extra": {
"testerAnalytics": {
"appId": "my-cool-app",
"apiKey": "hk_your_key_here",
"apiUrl": "https://saraayab.shop"
}
}
}
}All three values come from the Developer portal.
- appId — the exact app slug
- apiKey — the API key (starts with hk_) (required in production)
- apiUrl — https://saraayab.shop (HTTPS)
Create src/tester.ts and initialize the SDK
your project/src/tester.ts// src/tester.ts
import { initializeExpoTesterAnalytics } from 'tester-analytics-sdk/expo';
export const tester = initializeExpoTesterAnalytics();SecureStore keeps the persistent anonymous Tester ID, expo-constants reads the config from app.json. Both work in Expo Go.
Import the tracker in your app entry file
Choose the entry file that matches your project:
Classic Expo project (App.tsx or index.ts)
your project/App.tsx// App.tsx (top of the file) import './src/tester';
Expo Router project (app/_layout.tsx)
your project/app/_layout.tsx// app/_layout.tsx (top of the file) import '../src/tester';
Track screens from the navigator:
// Add these props to your existing NavigationContainer.
import { tester } from './src/tester';
onReady={() => {
const r = navigationRef.current?.getCurrentRoute();
if (r?.name) tester.setScreen(r.name);
}}
onStateChange={() => {
const r = navigationRef.current?.getCurrentRoute();
if (r?.name) tester.setScreen(r.name);
}}If you use Expo Router, track route changes with usePathname:
// app/_layout.tsx (inside your root layout component)
import { useEffect } from 'react';
import { usePathname } from 'expo-router';
import { tester } from '../src/tester';
const pathname = usePathname();
useEffect(() => {
tester.setScreen(pathname);
}, [pathname]);Track crashes with tester.reportCrash(message, stack).
Show the Tester ID in the app's settings screen via tester.getTesterId() and optionally set the contact name with tester.setContactName(name).
Run & verify
npx expo start
- Open the app once on the device.
- Open the app once. Events appear under the app you created.
- The tester shows as Active with version, device, opens, and activity history.
Done. After the first open, the tester appears as Active. Make sure you created the app and copied its API key.
Active usage tracking
For every tester the dashboard measures actual foreground / active usage time, grouped by date (e.g. tester_123 → 2026-09-07 → 42 minutes.
How it works:the SDK sends session signals (session_start, session_resume, session_pause, session_heartbeat. The server measures elapsed active time with its own clock between accepted signals — it never trusts client timestamps for totals. Time in the background is excluded automatically because a pause/close signal ends the measured interval.
Force-close / crash / no network:sessions auto-close after a no-signal window, so a lost pause or heartbeat can't inflate totals. Duplicate signals are ignored and a single continuous session is capped to a maximum length.
View it:theUsagetab in the dashboard shows today's totals, per-tester daily history, and date-range sums. The GET /api/usageendpoint backs it.
Privacy & opt-out
The SDK uses a stable anonymous Tester ID (a random UUID stored in secure storage) — no name, email, phone, or personal information is required. The only optional personal field is the contact name/channel the tester chooses to enter.
Testers can opt outat any time from the app's Settings → Testing & Privacy, and can erase their local + server-side data with tester.eraseMyData() Opting out stops future events from this app; it does not delete data already received. Use eraseMyData() to remove the tester's dashboard data.
Privacy policy for a temporary testing app
Add a clear, accurate disclosure to your privacy policy and store listing. Adapt this sample to your actual data, test period, hosting and retention:
During this app's [closed/open testing period], [developer/organization] uses TesterFlow to evaluate test builds, diagnose crashes and bug reports, and understand test-session activity. The app sends a random tester ID, app version, device information, screen/activity events, and foreground session signals to [hosting provider/region]. Crash details and bug-report information are sent when generated or submitted. Testers may optionally provide contact details. This data is used only to operate this testing program, not for advertising or production-user analytics. Collection will stop when [test end condition/date]. Data is retained for [actual retention period]. To stop future collection, use [in-app opt-out instructions]; to request deletion, use [erase instructions or contact details].
Only claim the collection is temporary or not used in production if that is true and you will stop or change collection when testing ends. TesterFlow does not automatically delete data when a test ends. Keep the privacy policy, Play Store listing/Data safety disclosures, and app behavior consistent. This sample cannot guarantee Google Play approval; check current Play policies for your app and testing track.
Notes & limitations
- Reinstall → new Tester ID. The ID lives in secure storage; reinstalling or clearing app data creates a new ID.
- Android only. This SDK uses secure storage and does not support expo start --web; use an Android device or emulator.
- Contact name is opt-in PII and only sent if the tester enters it. tester.eraseMyData() deletes dashboard and local data.