Reusable flows
The @ovok/native/ui module includes three higher-level components for common app tasks: pairing a nearby device, collecting a manual measurement, and presenting health-data access through an app-provided adapter. Each component handles its own local UI state. Your app supplies the provider, storage, persistence, permissions, patient context, and navigation that give the flow meaning in your product.
Use the App UI guide for the complete UI catalog, shared theme, and slot customization.
| Flow | Use it when | Your app supplies |
|---|---|---|
PairingFlow | Users need to choose and pair a discovered Bluetooth device | An active BTProvider, PairedDeviceStorage, and a code handler when the device requests one |
ManualEntryForm | Users need to enter a measurement defined by the SDK catalog | An async save callback, patient mapping, and product-specific labels |
HealthImportCard | You need an access-status and request UI around an existing health adapter | A HealthImportAdapter, permission policy, and any separate import service |
Adopt these independently. Pairing and health access do not depend on each other, and manual entry can be used without either provider.
Pair and remember a Bluetooth device
Render PairingFlow inside the Bluetooth provider that owns the current device session. The flow presents devices already discovered by that provider; it does not create a BLE manager or start a scan.
import { BTProvider, IntegratedDevices } from "@ovok/native/bt-management";
import { PairingFlow } from "@ovok/native/ui";
const acceptedDevices = [IntegratedDevices.BP2] as const;
<BTProvider bleManager={bleManager} acceptedDevices={acceptedDevices}>
<PairingFlow
storage={pairedDeviceStorage}
onPaired={(device) => analytics.track("device_paired", { id: device.id })}
onError={(error, device) => reportPairingError(error, device?.id)}
/>
</BTProvider>
storage implements async getItem(key) and setItem(key, value) methods. The flow stores a JSON array of device IDs under @ovok/native/paired-devices by default; set storageKey to keep a separate collection. It does not store credentials or device records.
When the runtime reports that pairing needs a code, the flow displays an input and passes its value and selected device to onPairingCode. Provide this callback to run the device- or platform-specific confirmation. A successful callback returns the UI to the pairing state; the device still needs to report that it is bonded before the flow remembers it and calls onPaired.
The default device row starts pairing. If users also need to forget remembered devices, render a custom row with renderDevice; its actions argument includes a forget() function. The flow exposes devices, remembered IDs, the selected device, status, and error to custom render callbacks.
Collect and save a manual measurement
ManualEntryForm uses the SDK's measurementFieldCatalog for the selected MeasurementTypeKey. Required fields, numeric bounds, and units come from that catalog. The form validates values and calls the app's async save function with a plain typed payload:
import { MeasurementTypeKey } from "@ovok/core";
import { ManualEntryForm, type ManualEntrySaveInput } from "@ovok/native/ui";
const saveMeasurement = async (input: ManualEntrySaveInput) => {
await measurementRepository.save({
patientId: activePatient.id,
...input,
});
};
<ManualEntryForm
measurementTypeKey={MeasurementTypeKey.bodyWeight}
saveMeasurement={saveMeasurement}
labelForField={(field) => measurementLabels[field] ?? field}
onSaved={() => navigation.goBack()}
/>
The app decides which patient owns the entry, how it is persisted or queued, and whether it should be converted into a FHIR resource. The callback must resolve only after the app considers the save complete; onSaved runs after it resolves. While the callback is pending, the form blocks another save and shows a loading state. Validation and rejected-save errors appear inline.
initialValues and recordedAt seed state when the form mounts. If the active measurement changes after that, remount with a new React key or manage editing state in the app. The built-in copy is English; provide title, labelForField, and labelForUnit for your product language and units.
Request health-data access through an adapter
HealthImportCard is a status and request surface for an app-owned HealthImportAdapter. Use it when the app already owns the platform authorization implementation:
import { HealthImportCard } from "@ovok/native/ui";
import type { HealthImportAdapter } from "@ovok/native/bt-management";
type HealthType = "steps" | "weight";
const adapter: HealthImportAdapter<HealthType> = {
getStatus: (type) => readAccessStatus(type),
request: async (types, background) => {
await requestHealthAccess(types, { background });
},
};
<HealthImportCard
types={["steps", "weight"]}
adapter={adapter}
labelForType={(type) => healthTypeLabels[type]}
progress={progressByType}
/>
getStatus returns authorized, shouldRequest, refused, or unsupported for each app-defined type. request(types, background) receives the whole type list and the background option. On mount and after a successful request, the card reads statuses again; it considers the card enabled only when every listed type is authorized.
Switching the card off only changes its enabled state. The adapter has no revoke, stop, or cancel method, so the control does not disable OS permissions or stop an import service. The component also does not configure HealthKit or Health Connect or start the SDK's DataSync pipeline. Use the native permission providers and import components documented in HealthKit and Health Connect when you need those integrations.
Keep app decisions at the app boundary
These flows are building blocks rather than end-to-end product screens. Keep these decisions in app-owned code:
- which devices and measurement types the user can select;
- where device IDs and measurements are stored;
- which patient or account owns each measurement;
- how health access is explained and requested;
- when to navigate after an operation succeeds;
- how errors, analytics, and translations fit the product.
Use the flow references for each component's props, callbacks, and lifecycle details.