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, MeasurementTypeForDevices } from "@ovok/native";
import type { IntegratedDevices } from "@ovok/native";

export interface ResultCallback<T extends IntegratedDevices> {
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 kinddata.data shape
IntegratedDevices.F4BodyWeightMeasurement (from @ovok/core)
IntegratedDevices.BP2BloodPressureMeasurement | EcgMeasurement
IntegratedDevices.SPO2 (Oxyfit)PulseOximeterMeasurement
IntegratedDevices.PC60WF (OxySmart)PulseOximeterMeasurement
Other IntegratedDevices.* entriesnever — devices are connectable but don't emit onResult yet

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.deviceData = { id, name, localName, sn, manufacturerData, measurementTypes }
// 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. onDeviceStatusChangedConnected.
  3. onDeviceStatusChangedMeasuring (carries measurementTypeKey).
  4. onResult — cuff finishes inflating + measuring, parsed result delivered.
  5. onDeviceStatusChangedConnected (back to idle-but-connected).

If the cuff fails to read a stable measurement, onResult is skipped and onError fires instead.

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.