Skip to main content

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:

  1. Initialize polyfillMedplumWebAPIs once.
  2. Create a durable OvokClient.
  3. Mount OvokProvider from @ovok/core.
  4. Mount the SDK ThemeProvider.
  5. Mount optional gesture, keyboard, bottom-sheet, and navigation providers needed by the screens in use.
  6. Render feature providers and screens below that shell.

The SDK does not export a catch-all provider. Each side-effect boundary is explicit:

BoundaryOwnsHost still owns
ThemeProviderPaper theme, bundled fonts, device-list contextApp theme choice and navigation theme
BTProviderPermission state, scan flow, device protocol lifecycleManager lifetime, accepted devices, uploads, UI policy
DataSync childrenPlatform authorization and import workData mapping, permission messaging, app persistence
SocketProviderSocket connection context and eventsServer URL, auth policy, subscriptions, reconnect UX
Auth componentsForm state and client callsNavigation, error policy, account recovery UX
Questionnaire providersForm/navigation state and response constructionResource persistence and submission policy
ui componentsThemeable presentation and small flow stateNavigation, 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:

  1. The host creates one BleManager and passes it to BTProvider.
  2. The provider requests permission and scans for the accepted declarations.
  3. onDeviceFound receives a wrapped device; the host decides whether to connect.
  4. The device protocol reports connection events, statuses, measurements, or errors.
  5. The host receives the result and decides whether to persist, upload, display, or discard it.

Health import follows a separate sequence:

  1. The app maps measurement types to platform identifiers and observation codes.
  2. An authorization provider reports the platform state.
  3. The app requests the missing permissions.
  4. A platform sync component reads records and reports progress/errors.
  5. 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.