onResult
Optional callback prop on <BTProvider>. Fires after the SDK has parsed a complete measurement from a connected device's GATT characteristics.
This is the callback you wire to your data layer — persisting Observations to FHIR, syncing to a backend, updating local state.
Signature
import type { DeviceData, DeviceKey, MeasurementTypeForDevices } from "@ovok/native";
export interface ResultCallback<T extends DeviceKey> {
id: string;
deviceData: DeviceData;
data: Omit<MeasurementTypeForDevices<[T]>, "caseType">;
}
onResult?: (data: ResultCallback<T[number]>) => void;
T is the tuple you pass as acceptedDevices — the callback is typed against the union of all device kinds you accept, so data.data is a discriminated union you can switch on.
Measurement type per device
The SDK's source-of-truth map is src/modules/bt-management/types/device-measurement-map.ts. Currently:
| Device kind | Reading shape |
|---|---|
Built-in or custom DeviceKey | MeasurementTypeForDevices<[T]> from the accepted declaration |
The exact map is intentionally exported as a type rather than duplicated in this page. It changes as device protocols are added; a custom declaration is typed from its declared measurement key.
The full measurement type definitions live in @ovok/core and are documented per measurement family. The caseType discriminator is stripped before reaching your callback — you don't need it.
Usage
import React, { useCallback } from "react";
import { BleManager } from "react-native-ble-plx";
import { BTProvider, IntegratedDevices } from "@ovok/native";
const bleManager = new BleManager();
const acceptedDevices = [IntegratedDevices.BP2, IntegratedDevices.SPO2] as const;
function MyApp() {
const handleResult = useCallback(async (data) => {
// data.id is the stable identity for this recorded reading.
// data.deviceData also includes optional manufacturerName and model values
// data.data = parsed measurement for the matched device kind
switch (data.deviceData.name) {
case IntegratedDevices.BP2:
// data.data is BloodPressureMeasurement | EcgMeasurement
if ("systolic" in data.data) {
await persistBp(data.data, data.deviceData);
} else {
await persistEcg(data.data, data.deviceData);
}
break;
case IntegratedDevices.SPO2:
// data.data is PulseOximeterMeasurement
await persistSpo2(data.data, data.deviceData);
break;
}
}, []);
return (
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
onResult={handleResult}
// ... other props
>
<YourScreens />
</BTProvider>
);
}
Timing relative to other callbacks
For a typical BP2 reading the sequence is:
onDeviceFound— peripheral matchesacceptedDevices, your handler connects + subscribes.onDeviceStatusChanged→Connected.onDeviceStatusChanged→Measuring(carriesmeasurementTypeKey).onResult— cuff finishes inflating + measuring, parsed result delivered.onDeviceStatusChanged→Connected(back to idle-but-connected).
If the cuff fails to read a stable measurement, onResult is skipped and onError fires instead.
deviceData.manufacturerName and deviceData.model come from the monitor's standard
Bluetooth Device Information service when the device reports them. They are optional;
use deviceTitle(deviceData) when presenting a device name so the SDK can fall back from
reported maker/model to the advertised name and then the catalog name.
import { deviceTitle } from "@ovok/native";
const label = deviceTitle(data.deviceData); // e.g. "OMRON HEM-7361T"
BP2 ECG diagramPoints are reported in millivolts. The device sends signed counts, which the
SDK converts with millivolts = count * 0.003098. Recordings persisted by older SDK versions
must be migrated by multiplying each stored BP2 ECG sample by 0.003098 before rendering or
syncing them; the SDK cannot rewrite data already stored by the host app.
Persisting to FHIR
@ovok/core exposes resource hooks (useCreate, useUpdate) and the OvokClient instance via useClient(). Convert the parsed measurement to an Observation shape and POST:
import { useClient } from "@ovok/core";
const client = useClient();
const handleResult = useCallback(async (data) => {
await client.create({
resourceType: "Observation",
status: "final",
subject: { reference: `Patient/${currentPatientId}` },
device: { reference: `Device/${data.deviceData.sn}` },
// ...map data.data fields onto LOINC codes for the measurement type
});
}, [client, currentPatientId]);
For multi-component measurements (BP carries systolic + diastolic + pulse, BP2 ECG carries a waveform), persist them as a single Observation with component[] rather than multiple Observations.
Don't confuse with DeviceStatus.Complete
There is no DeviceStatus.Complete enum value — the DeviceStatus enum has only 4 values. After onResult resolves, onDeviceStatusChanged reports Connected again, not a synthetic "Complete". If your UI needs a "Complete" label, set it in your own state when onResult fires.
Related
- BTProvider — full provider reference
- on-device-found — the upstream
device.connect()+ subscribe call - on-device-status-changed —
Measuringprecedes everyonResult - on-error — failure path when a measurement aborts