Architecture and lifecycle
This explanation describes the boundaries an integrating app owns. The SDK supplies React Native UI, native adapters, and typed lifecycle events; the app still owns navigation, authentication persistence, patient selection, upload policy, and clinical decisions.
The three application layers
App screens and navigation
|
| app-owned auth, patient, upload, persistence, telemetry
v
@ovok/native
|-- UI components and hooks
|-- Bluetooth discovery, protocols, and result delivery
|-- HealthKit / Health Connect authorization and import
|-- Android and iOS background entry points
v
@ovok/core + native platform services
@ovok/core owns the server-facing client, FHIR resources, and authentication
context. @ovok/native consumes that context where a component needs the active
client or patient, but it does not create an account, choose a patient, or upload a
measurement on behalf of the host unless a callback explicitly delegates that work.
Provider ownership
The normal root composition is:
- Initialize
polyfillMedplumWebAPIsonce. - Create a durable
OvokClient. - Mount
OvokProviderfrom@ovok/core. - Mount the SDK
ThemeProvider. - Mount optional gesture, keyboard, bottom-sheet, and navigation providers needed by the screens in use.
- Render feature providers and screens below that shell.
The SDK does not export a catch-all provider. Each side-effect boundary is explicit:
| Boundary | Owns | Host still owns |
|---|---|---|
ThemeProvider | Paper theme, bundled fonts, device-list context | App theme choice and navigation theme |
BTProvider | Permission state, scan flow, device protocol lifecycle | Manager lifetime, accepted devices, uploads, UI policy |
DataSync children | Platform authorization and import work | Data mapping, permission messaging, app persistence |
SocketProvider | Socket connection context and events | Server URL, auth policy, subscriptions, reconnect UX |
| Auth components | Form state and client calls | Navigation, error policy, account recovery UX |
| Questionnaire providers | Form/navigation state and response construction | Resource persistence and submission policy |
ui components | Themeable presentation and small flow state | Navigation, localization, storage, and backend calls |
Keep a side-effect provider mounted for the lifetime of the flow it owns. Unmounting and remounting a Bluetooth or sync provider can restart scans or authorization work.
Foreground data lifecycle
Bluetooth results follow this sequence:
- The host creates one
BleManagerand passes it toBTProvider. - The provider requests permission and scans for the accepted declarations.
onDeviceFoundreceives a wrapped device; the host decides whether to connect.- The device protocol reports connection events, statuses, measurements, or errors.
- The host receives the result and decides whether to persist, upload, display, or discard it.
Health import follows a separate sequence:
- The app maps measurement types to platform identifiers and observation codes.
- An authorization provider reports the platform state.
- The app requests the missing permissions.
- A platform sync component reads records and reports progress/errors.
- The active core client and patient context receive the imported observations.
Do not treat Bluetooth permission, health permission, and Ovok authentication as one state. They can succeed or fail independently.
Background lifecycle
Background BLE delivery persists a result before calling the host callback. Delivery is
at least once, so a process death between callback completion and queue removal can
repeat a result. Use the stable result id as the backend idempotency key and resolve
the callback only after the server accepts the result.
Android Health Connect background work is different: WorkManager wakes a headless JS
task, and the host restores its client and patient identity before calling
runAndroidHealthConnectSync. There is no React tree in that task, and the SDK cannot
serialize an authenticated client for the host.
The operating system controls when either background path runs. Native permissions, durable storage, restoration identifiers, service UUIDs, battery policy, and process state all affect delivery.
Choosing an import path
Use the root import for shared/auth/theme/Bluetooth-management APIs. Use a subpath for optional integrations so Metro does not resolve unrelated peers:
import { BTProvider } from "@ovok/native";
import { DataSync } from "@ovok/native/data-sync";
import { runAndroidHealthConnectSync } from "@ovok/native/background-sync";
See Public API for the complete mapping and Installation for the peer-dependency and native-build requirements.