Repository navigation
06 Android Application Architecture
The HealthBook AI phone app (internal code name: BabyCare) lives in mobile/ and is built with Expo, React Native and TypeScript. It is a thin client: every record comes from the same FastAPI backend the web app uses, authenticated with a bearer token held in the device's secure storage. Android is the primary target. The same code typechecks and bundles for iOS, but no iOS store pipeline exists.
- Sign in, register and request a password reset, with the session token stored in
expo-secure-store. - Show five tabs (Home, Records, Health, Share, More) for the selected family profile, plus a horizontal profile switcher.
- Upload and review documents, record caregiver events, run the Daily Health journal (with an offline queue), manage doctor shares and exports, and register for generic push reminders.
- Select the API per environment at build time, and refuse insecure transport outside local development.
| Input | Output |
|---|---|
EXPO_PUBLIC_ENV, EXPO_PUBLIC_API_URL[_LOCAL / _STAGING / _PRODUCTION], EXPO_PUBLIC_WEB_ORIGIN
|
extra.apiUrl, extra.webOrigin, app name, cleartext policy |
| User credentials | Bearer token in SecureStore key babycare_token
|
| API JSON | React Native views |
| Expo push token (with consent) |
POST /api/devices, device id saved in SecureStore |
| Offline Daily Health POSTs | SecureStore queue entries, flushed when the network returns |
App.tsx owns authentication state, the selected profile (pid), the active tab and a light "screen" stack used for forms (add medicine, log dose, add visit, edit vaccine, review OCR, and so on). One api(path, method, body, upload) callback adds Authorization: Bearer, parses errors and, on a 401, deletes the stored token and returns to sign-in. Mobile requests never use cookies, so the API's cookie CSRF rule does not apply to them (see Authentication and Session Security).
| Lifecycle hook | Behaviour |
|---|---|
| Startup | Read babycare_token from SecureStore. If no API URL is configured, show "Configuration needed" |
refresh() |
GET /patients, then the profile's documents, medicines, visits, vaccines and 60-day reminders in parallel |
AppState change to active
|
refresh() again, so data is current when the app resumes |
| Notification tap | Open More → Notifications and refresh |
| Logout | Disable this push device (DELETE /devices/{id}), POST /auth/logout, delete both SecureStore keys |
flowchart LR
subgraph PHONE["Android app"]
APPX["App.tsx<br/>auth, profile, tabs, api helper"]
SS["expo-secure-store<br/>babycare_token, push device id,<br/>offline queue"]
NOTIF["expo-notifications<br/>generic banner"]
end
CFGX["app.config.js and src/config.ts<br/>SERVER, WEB_ORIGIN, ENV"] --> APPX
APPX <-->|"token"| SS
APPX -->|"HTTPS, Bearer token"| API["FastAPI API<br/>hosted API origin"]
API -->|"JSON"| APPX
APPX -->|"register Expo push token"| API
WORKER["Reminder worker"] -->|"generic text only"| EXPO["Expo push service"]
EXPO --> NOTIF
APPX -->|"open reset or doctor links"| WEB["Web origin<br/>static hosting site"]
(Real deployments use placeholders such as https://<api>.onrender.com and https://<project>.web.app.)
flowchart TD
ROOT["App.tsx"] --> AUTH["Sign in or register"]
AUTH --> FP["ForgotPassword.tsx"]
ROOT --> FIRST["Create first profile"]
ROOT --> TABS["Tab bar"]
TABS --> HOME["HomeScreen"]
TABS --> REC["RecordsScreen"]
TABS --> HEALTH["HealthScreen"]
TABS --> SHAREX["ShareScreen"]
TABS --> MORE["MoreScreen"]
HEALTH --> LIFE["LifetimeScreen"]
SHAREX --> EXPS["ExportScreen"]
MORE --> DAILY["DailyCareScreen"]
DAILY --> STOOL["StoolIntake"]
MORE --> CAM["CameraScreen"]
MORE --> EXPS
ROOT --> REVIEW["CareScreens ReviewScreen<br/>OCR review and confirm"]
| File | Purpose |
|---|---|
HomeScreen.tsx |
Dashboard (/dashboard, /growth), upcoming reminders, quick links to upload, review, Daily Health and Health sub-tabs, enable push |
RecordsScreen.tsx |
Upload by category (prescription, lab, culture, vaccination card, discharge…), list and filter documents, run OCR, search records (/ask) |
HealthScreen.tsx |
Sub-tabs Lifetime, Illness, Fever, Antibiotics, Medicines, Labs, Vaccinations, Growth, Hospital, with charts. Actions such as verify medicine, log dose, confirm vaccine and reschedule visit are passed in from App.tsx
|
LifetimeScreen.tsx |
Lifetime Health dashboard, E. coli and correlation views, parent entries, evidence lookup, verification, structured questions |
ShareScreen.tsx |
Build a doctor share by categories, illness episode, date range or chosen documents. Preview, create, revoke and view the access log |
ExportScreen.tsx |
Export presets with a server-side preview (/export/preview) and PDF download |
MoreScreen.tsx |
Sections: Family profiles, Notifications (acknowledge or snooze), Family access (invite or revoke), Doctor shares, Export PDF, Daily care, Home camera, Settings, Deployment info |
DailyCareScreen.tsx |
Daily Health journal for a day, medicines today, care settings, stool entries, offline queue |
StoolIntake.tsx |
Pick one or many stool photos from the camera, gallery or files, upload them for intake, review detected times and confirm |
CameraScreen.tsx |
Home activity cameras: test, worker key, disable, purge recordings, review observations, add them to the journal |
CareScreens.tsx |
ReviewScreen for OCR review and confirmation of a document. Also exports CareHub, which App.tsx does not currently use |
ForgotPassword.tsx |
Sends the reset email request. The new password is set on the web origin |
src/ui.tsx holds the design tokens (C) and shared components: Screen, Title, Card, Btn, Chip, Metric, Note, Segments, Input, Source and the date formatters. src/charts.tsx provides touch-first SVG charts (TrendChart, SpanTimeline, Bars) built on react-native-svg. It follows the same rules as the web chart library: one series per chart, no inferred values, and a single measurement shown as a value card rather than a line.
app.config.js reads EXPO_PUBLIC_ENV (local by default, or staging / production):
| Env | API source | Cleartext HTTP | App name |
|---|---|---|---|
| local |
EXPO_PUBLIC_API_URL_LOCAL, else http://10.0.2.2:8000 (emulator to host) |
allowed (usesCleartextTraffic: true) |
"HealthBook AI local" |
| staging |
EXPO_PUBLIC_API_URL_STAGING (required) |
disabled | "HealthBook AI staging" |
| production |
EXPO_PUBLIC_API_URL_PRODUCTION, else the public production placeholder |
disabled | "HealthBook AI" |
For any environment other than local, the config throws at build time if the API URL contains localhost, 127.0.0.1, 10.0.2.2 or starts with http:. src/config.ts exposes SERVER, WEB_ORIGIN, ENV, API_CONFIGURED and isSecureTransport, and never falls back to localhost outside local.
The user must opt in. syncNotifications() explains that Expo, Apple or Google receive the device token and a generic message. It then creates the Android channel "Care reminders", requests permission, gets the Expo push token (which needs EXPO_PUBLIC_EAS_PROJECT_ID and a native build), posts it to /api/devices with consent_generic_push: true, stores the device id and cancels any locally scheduled notifications. The notification handler shows a banner without sound. See Notifications and Reminder Architecture.
DailyCareScreen.tsx keeps a queue in SecureStore, with an index key plus one care_q_<id> key per entry. If a Daily Health POST fails because the network is down, queueAdd() stores {path, method, body} and tells the user "Saved offline". queueFlush() runs when the screen loads. It replays entries in order, deletes the ones that succeed, keeps the ones that still fail for network reasons, and drops entries the server rejects. Only POST mutations are queued. Reads are never cached offline.
-
Local APK:
scripts/build_android.ps1 -Env local|staging|production [-ApiUrl …] [-WebOrigin …] [-Variant release|debug]. It runsnpx expo prebuild --platform android, limits ABIs toarm64-v8a,x86_64, mirrors the project to a short path to work around the Windows path-length limit, then runs GradleassembleReleaseorassembleDebugand copies the APK toandroid-builds/. Without upload-keystore environment variables, the release variant is signed with the Android debug key. That is fine for personal testing but is not a store release. -
CI: the
mobilejob runsnpm run typecheckandnpx expo exportfor Android and iOS. Theandroid-apkjob (onmainor manual dispatch) runs prebuild withEXPO_PUBLIC_ENV=productionand./gradlew assembleDebug, then uploads the debug APK as an artifact kept for 30 days. -
eas.jsondefinesdevelopment,previewandproductionprofiles for optional EAS builds.
- The token is stored only in SecureStore and is deleted on 401 and on logout.
- Staging and production builds cannot ship with a local or HTTP API.
- Original documents open through short-lived signed URLs (
POST /documents/{id}/signed-access), so the token never appears in the URL.
The app shows the same distinctions as the web app. Logging a dose is labelled "a caregiver report, not an independently verified administration". Marking a visit attended "will NOT say any medication was administered". Editing vaccine dates removes confirmation. Push banners are generic, and receiving one is not proof that a dose was given.
mobile/App.tsx, mobile/*Screen.tsx, mobile/CareScreens.tsx, mobile/StoolIntake.tsx, mobile/ForgotPassword.tsx, mobile/src/ui.tsx, mobile/src/charts.tsx, mobile/src/config.ts, mobile/app.config.js, mobile/eas.json, scripts/build_android.ps1, .github/workflows/ci.yml.
System Architecture · Web Application Architecture · Authentication and Session Security · Daily Health and Care Calendar · Stool Photo Pipeline · Notifications and Reminder Architecture · Deployment and CI/CD
Overview
Platform
Identity and access
Medical records
- 10 Document Ingestion
- 11 OCR and Human Verification
- 13 Medicines and Administration
- 14 Vaccination Model
- 15 Labs, Growth, Longitudinal
- 16 Illness and Lifetime Health
Daily care
Output and sharing
Security
Engineering
- 26 Testing and Validation
- 27 Deployment and CI/CD
- 28 Technology Stack
- 29 Design Decisions
- 30 Limitations
- 31 Roadmap