Skip to main content

Reusable SDK features

The SDK exposes integration primitives and optional composition UI, not an application workflow. A host can enable one supported device, register a custom device, or use the complete catalog. Patient selection, authentication, server upload, storage, notifications, and navigation remain host decisions; the optional ui module provides themeable flows that accept the host's theme and primitive slots.

Bluetooth hooks​

Render these hooks below BTProvider:

const { state, requestPermission } = useBluetoothState({ bleManager });
const { devices } = useNearbyDevices();
const { paired, pair, forget } = usePairedDevices({ storage });
const connection = useDeviceConnection(devices[0]?.id);
const { measurements } = useMeasurements(onResult, {
policy: "first-reading-per-kind-per-connection",
});

useBluetoothState reports on, off, unauthorized, or unsupported. The device, connection, and measurement hooks expose SDK events without prescribing how a host renders or uploads them. usePairedDevices requires an app-supplied storage adapter; the SDK does not assume an account, patient, or storage key.

resultPolicies can be supplied to BTProvider per device. Available policies are all, daily-totals-only, and first-reading-per-kind-per-connection; each device can also override the policy by MeasurementTypeKey through byMeasurement. Connection events include pairing, bonded, history-download progress, and standard battery level. History-download progress is emitted once per accepted history reading; progress.total is currently omitted, so consumers should treat progress.current as a delivered-reading count rather than a percentage denominator.

Every delivered result has a stable, non-enumerable id: JSON serialization, object spread, and Object.keys do not include it, so read it directly before copying the payload. A device history record sent again receives the same identity when its device identity and measurement payload are unchanged.

Background sync and storage​

useBackgroundSync exposes enabled, setEnabled, and the states on, off, restricted, and blocked. It toggles the manager's scan policy; it does not itself run the result queue or schedule uploads. Supply canEnable and onEnabledChange when the host has additional platform policy. BTProvider.backgroundSync and its BackgroundSyncCoordinator persist before delivery and retry failed deliveries; the queue passes queuedAt, attempt, queueId, and the stable result id to the host callback.

For credentials needed by a background relaunch, use createBackgroundSafeClientStorage with an injected SecureStore-compatible adapter. The exported BackgroundSafeSecureStore, BackgroundSafeStorageOptions, and BackgroundSafeClientStorage types describe that boundary. Reads that fail before first unlock never delete the stored value; call reload after unlock and retry. The adapter does not own authentication or choose a host key. Test the supplied adapter on each target OS: keychainAccessible is the numeric SecureStore.AFTER_FIRST_UNLOCK value (the default is 0), and secure-store writes can still fail before native keychain setup or first unlock. On iOS, the current expo-secure-store integration can reject the adapter's AFTER_FIRST_UNLOCK write options, so writes may throw instead of becoming background-safe; verify that the installed native version accepts the adapter's option shape before relying on writes for a background relaunch. Surface write failures and provide a host-owned retry path.

The public boundary is intentionally small and app-owned:

interface BackgroundSafeSecureStore {
getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;
setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;
deleteItemAsync?(key: string, options?: Record<string, unknown>): Promise<void>;
}

interface BackgroundSafeStorageOptions {
secureStore: BackgroundSafeSecureStore;
keyListKey?: string;
keychainService?: string;
keychainAccessible?: number;
}

interface BackgroundSafeClientStorage {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem(key: string): Promise<void>;
clear(): Promise<void>;
reload(): Promise<void>;
}

Health import​

useHealthImport accepts a host-provided adapter for status and authorization. Its getStatus(type) result is one of authorized, shouldRequest, refused, or unsupported; request(types, background) receives the complete requested type list and the hook-level background flag. The generic hook has no per-type backgroundAccess field. Platform-specific Android background permission status belongs to DataSync.AndroidHealthConnectAuthorizationProvider.

Expo native setup​

The optional @ovok/native config plugin only adds capabilities explicitly enabled by the host:

module.exports = [
["@ovok/native", {
bluetooth: { background: true },
healthKit: true,
healthConnect: true,
backgroundSync: true,
notifications: false,
}],
];

The plugin does not add the expo-notifications plugin unless notifications: true. bluetooth.background enables iOS background-central configuration, while backgroundSync enables the Android background service path. With healthConnect: true, a truthy backgroundSync value also enables scheduled Health Connect work. Inspect the merged native project. Native modules still require a development build; Expo Go cannot load them.

Test kit​

createFakeBleManager and replayRecordedFrames allow scan, parser, policy, and hook tests to run without a peripheral. Apps can keep their own recorded fixture registry with defineRecordedDeviceFrames and replay only the devices they support.

Error contract​

Native failures can be normalized with toBluetoothError. The stable BluetoothErrorCode is separate from the original error and optional translation key. Existing measurement translation-key errors remain compatible with earlier callbacks.

Release policy​

Behavior changes, new hooks, result identity changes, queue semantics, and native setup changes are minor-release work. Patch releases are reserved for fixes that do not change runtime behavior. A release that raises the required @ovok/core range must release core first, then update the native peer range and package version.