Skip to content

06 Android Application Architecture

Anubha Parashar edited this page Oct 10, 2026 · 1 revision

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.

Responsibilities

  • 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.

Inputs and outputs

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 shell and navigation

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"]
Loading

(Real deployments use placeholders such as https://<api>.onrender.com and https://<project>.web.app.)

Screens

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"]
Loading
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

Shared UI and charts

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.

Environments and transport security

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.

Push registration

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.

Offline queue

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.

Build

  • Local APK: scripts/build_android.ps1 -Env local|staging|production [-ApiUrl …] [-WebOrigin …] [-Variant release|debug]. It runs npx expo prebuild --platform android, limits ABIs to arm64-v8a,x86_64, mirrors the project to a short path to work around the Windows path-length limit, then runs Gradle assembleRelease or assembleDebug and copies the APK to android-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 mobile job runs npm run typecheck and npx expo export for Android and iOS. The android-apk job (on main or manual dispatch) runs prebuild with EXPO_PUBLIC_ENV=production and ./gradlew assembleDebug, then uploads the debug APK as an artifact kept for 30 days.
  • eas.json defines development, preview and production profiles for optional EAS builds.

Security boundaries

  • 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.

Clinical safety rules

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.

Implementation

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.

Related pages

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

Clone this wiki locally