Skip to main content

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 kindReading shape
Built-in or custom DeviceKeyMeasurementTypeForDevices<[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:

  1. onDeviceFound — peripheral matches acceptedDevices, your handler connects + subscribes.
  2. onDeviceStatusChanged → Connected.
  3. onDeviceStatusChanged → Measuring (carries measurementTypeKey).
  4. onResult — cuff finishes inflating + measuring, parsed result delivered.
  5. 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.