Tech Stack — Mobile
Parent: Tech Stack Overview
Applies to apps/dashboard-mobile — the React Native iOS/Android app targeting client/tenant users only. The DM portal remains web-only.
Framework
| Technology | Version | Notes |
|---|---|---|
| React Native | 0.83.4 | New Architecture enabled (Fabric + JSI) |
| Expo | SDK 55 | Managed workflow — Expo CLI, EAS Build |
| React | 19.2 | Latest React with concurrent features |
| TypeScript | v5.x strict | Shared types from @leadmetrics/common |
Expo managed workflow — not bare. Build and distribution via EAS (Expo Application Services). Metro bundler for development.
New Architecture (Fabric + JSI): Enabled for better scroll performance on large lists and native module interop for biometric auth.
Bundle ID / Package: ai.leadmetrics.mobile
Navigation
| Technology | Version | Notes |
|---|---|---|
| @react-navigation/native | v7 | Root navigation container |
| @react-navigation/native-stack | v7 | Stack navigator |
| @react-navigation/bottom-tabs | v7 | Bottom tab bar |
Deep link scheme: leadmetrics:// — handles push notification taps and OAuth callback redirects via expo-linking.
State Management
| Technology | Version | Purpose |
|---|---|---|
| @tanstack/react-query | v5.62 | Server state — API caching, background refetch, optimistic updates |
| @tanstack/react-query-persist-client | v5.62 | Persist query cache to MMKV across app restarts |
| @tanstack/query-sync-storage-persister | v5.62 | MMKV storage adapter for persistence |
Stale content is shown while fresh data loads. Write mutations (approvals, saves) require connectivity — they are not queued offline.
Forms & Validation
| Technology | Version | Notes |
|---|---|---|
| React Hook Form | v7.56 | Uncontrolled forms, same as web dashboard |
| @hookform/resolvers | v3.10 | Zod resolver |
| Zod | v3.24 | Shared schemas from @leadmetrics/common |
Push Notifications
| Technology | Package | Version | Platform |
|---|---|---|---|
| Firebase Cloud Messaging | @react-native-firebase/app + @react-native-firebase/messaging | v21 | Android + iOS (via APNs gateway) |
| Expo Notifications | expo-notifications | SDK 55 | Token management, foreground handling |
Token lifecycle: Tokens are registered with the API on first login and updated on rotation via the mobile patterns (crypto.randomUUID for device ID, per-item endpoints). Push device tokens stored in PushDeviceToken model.
Notification types triggered by the platform:
- Activity/approval created or updated
- Agent run completed or failed
- New content ready for client review
- Budget or goal alerts
Authentication
| Technology | Package | Purpose |
|---|---|---|
| Custom JWT (HS256) | @/lib/api (fetch wrapper) | Email/password login via POST /auth/v1/login — same Fastify API as web portals |
| Biometric re-auth | expo-local-authentication | Face ID / fingerprint for unlock session flow |
| OAuth browser | expo-web-browser | OAuth popups for channel connections |
| Secure token storage | expo-secure-store | Refresh token stored in OS keychain/keystore |
Token flow:
- Access token: in-memory (React state / query client)
- Refresh token:
expo-secure-store(OS keychain) - Silent refresh: AbortController-wrapped fetch with 401 retry
- Unlock session:
unlockSession()auth pattern with biometric gate
Local Storage
| Technology | Package | Version | Purpose |
|---|---|---|---|
| MMKV | react-native-mmkv | v3.1 | Fast key-value store for TanStack Query persistence + settings |
| expo-secure-store | expo-secure-store | SDK 55 | Secure storage for refresh token |
Animations & Gestures
| Technology | Package | Version |
|---|---|---|
| Reanimated | react-native-reanimated | v4.2 |
| Gesture Handler | react-native-gesture-handler | v2.30 |
| Worklets | react-native-worklets | v0.7 |
| Screens | react-native-screens | v4.23 |
| Safe Area | react-native-safe-area-context | v5.6 |
Real-Time (SSE)
| Technology | Package | Version | Purpose |
|---|---|---|---|
| react-native-sse | react-native-sse | v1.2 | SSE client for live agent output and activity status |
SSE pre-refresh pattern: token is refreshed before opening the SSE connection to avoid mid-stream 401s.
Markdown
| Technology | Package | Version |
|---|---|---|
| react-native-markdown-display | v7.0 | Renders strategy, context, and content markdown in native views |
UI Patterns
There is no shared component library between web and mobile (web uses custom Tailwind, mobile uses native React Native components).
Mobile-specific patterns:
FlatList/SectionListfor all scrollable lists — neverScrollViewfor long lists- Pull-to-refresh on all list screens
- No inline editing (web-only feature) — mobile is read + approve/reject only
- No DM portal actions — mobile is client-facing only
expo-local-authenticationfor biometric lock screen- Per-item API endpoints (not bulk) for all mutations
Shared Code with Web
| Shared package | What’s reused |
|---|---|
@leadmetrics/common | TypeScript types, Zod validation schemas, utility functions |
Not shared: UI components, routing, auth flows, storage — all platform-specific. The mobile app does not use @leadmetrics/ui (DOM-based React components).
Build & Distribution
| Platform | Tool | Process |
|---|---|---|
| Android | EAS Build + Gradle | Build → sign → Play Console internal track |
| iOS | EAS Build + Xcode | Build → sign → TestFlight (beta) → App Store |
Android emulator setup (dev):
- JDK 17, Android SDK, AVD with snapshot boot enabled
- API host:
10.0.2.2(Android emulator maps to machine’s localhost) - MSYS path fix required on Windows for Metro
- UIAutomator coordinates used for login in automated tests
Metro: restart Metro before emulator login after dependency changes.
Testing
| Layer | Technology | Version | Scope |
|---|---|---|---|
| Unit | Jest | v29.7 | Business logic, utilities |
| Component | @testing-library/react-native | v13.3 | Component behaviour |
| Framework | jest-expo | v55 | Expo-aware Jest preset |
No Playwright E2E for mobile — manual testing + UIAutomator for emulator automation.
Error Monitoring
| Technology | Purpose |
|---|---|
| Sentry (React Native) | Crash reporting, JS error tracking, performance monitoring (production only) |
Build-Time Configuration
Mobile apps do not use .env files. Config is injected via expo-constants at build time and via Expo’s app.config.js / EAS environment variables.
import Constants from "expo-constants";
const API_URL = Constants.expoConfig?.extra?.apiUrl ?? "http://10.0.2.2:3003";Key build variables:
API_URL=https://api.leadmetrics.io
APP_ENV=development | staging | production
GOOGLE_SERVICES_FILE= # google-services.json path for FCM (Android)
SENTRY_DSN= # production onlySecrets never enter the mobile bundle — all sensitive operations go through the Fastify API.