MedLens is a single React Native / Expo application with a strict separation between the navigation shell and the domain logic. Screens live under app/ (Expo Router, file-based routing); all data access, OCR, storage, and business logic live under src/. Components never issue raw SQL — every database read or write goes through a repository class.
See the Data Model for the SQLite schema and the OCR Pipeline for the capture-to-record flow.
Stack
| Layer | Choice | Why |
|---|---|---|
| Framework | Expo SDK 55 (managed workflow) | Camera, filesystem, and SQLite without ejecting |
| Navigation | Expo Router (file-based) | React Navigation underneath, with file-based convenience |
| State | React Context + useReducer | No external state libraries — most state lives in SQLite, not memory |
| Local DB | expo-sqlite | Local-first structured storage for all medical records |
| OCR | ML Kit (on-device) | Works offline; no data leaves the device on the default tier |
| AI structuring | Optional cloud API | Higher-accuracy extraction the user explicitly opts into |
| Charts | Victory Native | Time-series charts with native SVG rendering |
| Photo storage | expo-file-system | Structured local directory layout |
| Crypto | expo-crypto | UUID generation and at-rest encryption |
Because the project depends on native modules that cannot run in Expo Go, it requires a development build. See the Developer Guide.
Navigation shell (app/)
Expo Router maps the file tree to routes. The shell is a tab bar plus a set of dynamic detail routes:
_layout.tsx— root layout: providers and database initialization(tabs)/— the five primary tabs:- Home — dashboard with latest readings, upcoming meds, recent events
- Capture — camera viewfinder plus manual entry
- Meds — active medications, administration log, schedule
- Labs — lab results and trend charts
- Vitals — vital signs and trend charts
review/[photoId]— the post-OCR confirm/edit screenphoto/[photoId]— photo detail and the records linked to it (the audit view)trend/[metricName]— full drilldown for a single metricpatient/— patient list, detail/edit, and add-new (the schema supports multiple patients)settings— privacy tier, storage usage, export
Domain layer (src/)
db/— schema and migration runner, time-series trend queries, and the repository classes (patient, medication, vital, lab, event, photo). Migrations run inside an exclusive transaction against a version table.ocr/— the extraction pipeline and its tiered backends. See OCR Pipeline.storage/— filesystem operations for photos plus the bridge that keeps the filesystem and SQLite in sync.models/— all TypeScript interfaces and shared constants.utils/— trend math (delta calculation, context-aware coloring) and date helpers.context/— the global app state, the database instance provider, and the settings provider.components/— reusable UI: delta badges, reading cards, the chart wrappers, the medication timeline, the review card, and flag badges.notifications/— local reminder scheduling and tap routing, under strict PII rules (see Privacy and Safety).
State model
State is intentionally thin because the durable record lives in SQLite, not in memory. Three contexts cover it:
- App context — the active patient, the patient list, and a loading flag.
- Settings context — the extraction tier, unit preferences, theme, and the lock-screen preference. The optional API key lives in the device secure store, never in SQLite.
- Database context — the singleton database instance and a readiness flag.
Capture data flow
The core loop, from shutter to chart:
- Camera shutter saves the file and a photo record.
- The OCR pipeline runs against the captured image at the selected tier.
- The raw OCR text is stored back onto the photo record.
- The user lands on the review screen and sees the extracted records.
- The user confirms or edits each record.
- Each confirmed record is persisted through its repository.
- The dashboard and charts update automatically.
Design conventions
- TypeScript strict mode; functional components with hooks only.
- All dates are ISO 8601 strings; all IDs are UUIDs from expo-crypto.
- Dark theme by default — the app is used in dim hospital rooms.
- Minimum 48dp tap targets; primary actions reachable one-handed.