React Native
Full native screen capture on Android and iOS. Touch trails on Android (iOS on the roadmap). Session resumption across backgrounding. Works with Expo dev clients and bare React Native 0.76+.
Install
# Expo
npx expo install @galacha/react-native
# Bare RN
npm install @galacha/react-native
cd ios && pod installRebuild the native binary
This package ships Kotlin and Swift. After installing, a JS reload will not pick up the native module . you have to rebuild the dev client:
# Expo managed
npx expo prebuild
npx expo run:android
npx expo run:ios
# EAS
eas build --profile development --platform android
eas build --profile development --platform iosFirst build is slow (3–8 minutes on a cold cache). Subsequent builds are incremental.
Initialize
The SDK is a default export. Import it once, call .init() inside a mount effect, and wrap your app tree with <TouchCaptureView> so touch events have a place to land.
// app/_layout.tsx (Expo Router)
import { useEffect } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import Galacha, { TouchCaptureView } from "@galacha/react-native";
export default function RootLayout() {
useEffect(() => {
Galacha.init({
projectKey: process.env.EXPO_PUBLIC_GALACHA_PROJECT_KEY!,
});
}, []);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<TouchCaptureView buffer={Galacha.getBuffer()!}>
{/* rest of your providers + navigation */}
</TouchCaptureView>
</GestureHandlerRootView>
);
}Env var:
# .env
EXPO_PUBLIC_GALACHA_PROJECT_KEY=your_project_key_from_the_dashboardThe project key is the long hex string visible on your project page in the dashboard. It's not the same as the display name or the project UUID . it's the token the SDK authenticates with.
Identify the user
Two positional arguments: a string userId and an optional traits object.
// After login
useEffect(() => {
if (!user) return;
Galacha.identify(String(user.id), {
email: user.email,
name: `${user.first_name} ${user.last_name}`,
phone: user.phone_number,
});
}, [user]);All future events in this and subsequent sessions are tied to the user.
Capture tuning
All defaults shown . change anything you need. Defaults are sized for ~3-minute sessions at ~300KB.
Galacha.init({
projectKey: "...",
screenCaptureFps: 1, // frames per second
screenCaptureQuality: 0.4, // JPEG quality, 0–1
screenCaptureScale: 0.35, // frame downscale factor
captureTouches: true,
captureNetwork: true,
captureErrors: true,
maskTextInputs: true,
flushIntervalMs: 5000,
flushMaxEvents: 50,
sessionTimeoutMs: 30 * 60 * 1000, // resume-same-session window
});Raising screenCaptureFps or screenCaptureQuality directly increases session size on the wire and in storage.
Privacy . mask specific views
Wrap anything you don't want captured. The view is rendered as a solid block in the replay frames:
import { GalachaPrivate } from "@galacha/react-native";
<GalachaPrivate>
<CreditCardForm />
</GalachaPrivate>Privacy . full mode
Use on sensitive screens. Every frame is fully masked until you turn it off:
import { useEffect } from "react";
import Galacha from "@galacha/react-native";
function CheckoutScreen() {
useEffect(() => {
Galacha.setPrivacyMode(true);
return () => Galacha.setPrivacyMode(false);
}, []);
// ...
}Touch trails (Android, v0.8.0+)
Touch events are throttled natively to ~30Hz. Swipes and scrolls appear in the replay player as fading purple strokes with direction arrows on release.
No configuration . it's on by default as long as <TouchCaptureView> wraps your app. iOS touch trails are on the roadmap; iOS still captures screen frames.
To disable entirely:
Galacha.init({
projectKey: "...",
captureTouches: false,
});Custom events
Galacha.track("add_to_cart", { sku: "ABC-123", price: 49_990 });Names are capped at 200 characters. Properties must be JSON-serializable.
Manual lifecycle
Most apps never need these, but they exist:
| Method | Purpose |
|---|---|
Galacha.flush() | Force-flush the buffer to the API right now |
Galacha.stop() | Stop recording and tear down listeners |
Galacha.getSessionId() | Current session ID, or null |
Galacha.getVisitorId() | Persistent anonymous visitor ID |
Galacha.isReady() | true if init() has completed |
Common pitfalls
| Problem | Fix |
|---|---|
| Expo Go does nothing | This SDK has native modules. Use a dev client (expo run:android or EAS build). |
| Upgraded SDK, new features missing | You forgot to rebuild the native binary. JS reloads don't pick up Kotlin/Swift changes. |
TouchCaptureView throws about buffer | Call init() before rendering the wrapper . or use Galacha.getBuffer()! after a mount effect. |
| Touch trails not showing | You're watching a session recorded before 0.8.0 was installed, or TouchCaptureView isn't in the tree. |
| Zombie session in the dashboard | The app crashed before the first metadata event flushed. Zombies are never charged. |
| Main-thread stutter | Don't block JS with heavy sync work. Screen capture runs on a background thread already. |