Background sync
Background work is a set of platform-specific delivery paths, not a single SDK scheduler. Pick the path that matches the data source, configure its native capabilities, and restore the application state it needs when the operating system starts work outside the foreground app.
| Your app needs to… | Use | Reference |
|---|---|---|
| Deliver Bluetooth readings through a durable queue | BTProvider.backgroundSync | Queue Bluetooth readings |
| Keep a Bluetooth scan active on Android | BTProvider's foreground service, or AndroidBluetoothForegroundService when you own the scan | Run an Android BLE foreground service |
| Import Health Connect data after Android stops the process | WorkManager plus a registered headless task | Schedule Health Connect imports |
| Import HealthKit updates while the app is backgrounded | AppleHealthSync.backgroundDelivery | Receive HealthKit updates |
The operating system controls when background work runs. These APIs do not promise continuous execution or an exact schedule. See installation and native setup for the config plugin options and rebuild requirements.
Queue Bluetooth readings
BTProvider.backgroundSync persists each result before calling its asynchronous
onResult handler. Supply durable storage so the queue survives process death:
import { BTProvider, IntegratedDevices } from "@ovok/native/bt-management";
const acceptedDevices = [IntegratedDevices.BP2] as const;
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
backgroundSync={{
enabled: true,
storage: {
getItem: (key) => measurementStore.getItem(key),
setItem: (key, value) => measurementStore.setItem(key, value),
},
onResult: async (result) => {
await uploadMeasurement(result, { idempotencyKey: result.id });
},
maxRetries: 3,
retryBackoffMs: 30_000,
}}
onError={({ error }) => reportBluetoothError(error)}
>
<MeasurementScreen />
</BTProvider>;
The store must implement asynchronous getItem and setItem and persist data across
app restarts. Choose storage and retention rules that fit the sensitivity of your
measurement data. The onResult promise is the queue's acknowledgement: resolve it
only when your server has accepted the result. Reject it to leave the item queued for
retry.
Delivery is at least once. The queue removes an item after onResult resolves, so
a process exit between server acceptance and local removal can deliver it again. Use
result.id as a server-side idempotency key. queueId identifies one enqueue
operation; attempt is the current delivery attempt; queuedAt is the enqueue time.
The default is three attempts with a 30-second exponential backoff base. Exhausted
items remain persisted and are retried when the provider next flushes the queue, such
as when it mounts or the app returns to the foreground. The queue has no public
inspection or manual-flush API.
BTProvider.onResult is a separate immediate measurement callback. With background
sync enabled it still fires when a measurement arrives, while
backgroundSync.onResult handles acknowledged queue delivery. Keep these roles
separate if you need server delivery to be durable.
Restore iOS Bluetooth state
Create the BLE manager once, outside React renders, with a stable restoration identifier:
import { createBackgroundBleManager } from "@ovok/native/bt-management";
export const bleManager = createBackgroundBleManager({
restoreStateIdentifier: "com.example.app.bluetooth",
});
Use the same identifier in backgroundSync.restoreStateIdentifier. Enable
bluetooth.background in the Expo config plugin and rebuild the native app. The SDK
restores known peripherals and flushes queued results when iOS restores the BLE
manager. Keep BTProvider mounted for the monitoring session and make its callback
and storage dependencies stable.
iOS background scanning needs service UUID filters. The SDK derives UUIDs for these
built-in devices: BP2, F4, PC60WF, BC01, VEROVAL_DUO_CONTROL, and
VEROVAL_ECG. Add backgroundSync.serviceUUIDs for any other built-in device whose
services should be scanned. Custom device definitions derive their UUIDs from
mainServices.
CoreBluetooth decides when it restores the app and delivers events. A restoration identifier and background mode do not guarantee continuous scanning.
Run an Android BLE foreground service
When backgroundSync.enabled is true, BTProvider manages the Android connected-device
foreground service with the scan. Configure its notification and optional battery
optimization request in the provider options:
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
backgroundSync={{
enabled: monitoring,
storage: measurementStorage,
onResult: uploadMeasurement,
android: {
foregroundService: {
notificationTitle: "Bluetooth monitoring",
notificationBody: "Listening for health measurements",
requestBatteryOptimizationExemption: true,
},
},
}}
>
<MeasurementScreen />
</BTProvider>
The requestBatteryOptimizationExemption option asks Android to show its system
consent UI while the app is active; it does not grant an exemption. Explain the
request in your product UI and check the current state with
isAndroidBatteryOptimizationIgnored after the app resumes. The foreground service
can run without the exemption, subject to Android's power policies.
If your app owns the BLE manager and scan lifecycle outside BTProvider, use the
standalone AndroidBluetoothForegroundService or
the start/stop functions from @ovok/native/background-sync. Use one service owner
per scan; BTProvider already starts its own service. Android service permissions
must be present in the native build. See installation and native setup.
Schedule Health Connect imports
Android Health Connect background work is separate from Bluetooth. WorkManager wakes a headless JavaScript task; that task must restore the authenticated client and patient identity because there is no React tree and the SDK cannot serialize a client or credentials.
Register the task once from the application entry point, before Android needs to start the headless runtime:
import {
registerAndroidHealthConnectBackgroundTask,
runAndroidHealthConnectSync,
} from "@ovok/native/background-sync";
registerAndroidHealthConnectBackgroundTask(async ({ reason }) => {
const { client, patientId } = await restoreHealthSyncSession();
reportBackgroundRun(reason);
await runAndroidHealthConnectSync({
client,
patientId,
dataToSync: androidDataToSync,
onError: reportHealthSyncError,
});
});
Schedule periodic work from a foreground flow after the user enables it:
import {
cancelAndroidHealthConnectSync,
scheduleAndroidHealthConnectSync,
} from "@ovok/native/background-sync";
await scheduleAndroidHealthConnectSync({
intervalMinutes: 15,
notificationTitle: "Health data sync",
notificationBody: "Syncing recent Health Connect records",
});
// When the user turns scheduled import off:
await cancelAndroidHealthConnectSync();
WorkManager's minimum interval is 15 minutes; it may run later because Android
applies its own scheduling and power policies. The worker also requires network
connectivity. If you pass a custom taskName to scheduleAndroidHealthConnectSync,
register the headless task with that same name. The default is
OvokHealthConnectBackgroundSync.
Android runs the headless task through a foreground service and shows a notification
while the work runs. Set notificationTitle and notificationBody when scheduling
to provide app-appropriate copy.
Request Health Connect's separate background-read permission with
backgroundAccess on AndroidHealthConnectAuthorizationProvider, in addition to
the foreground record permissions. Configure the Health Connect background service
in the Expo plugin and rebuild. See the authorization provider
and Health Connect guide.
runAndroidHealthConnectSync performs one import without rendering
AndroidHealthSync. Pass the same
dataToSync mapping used by the foreground component. onProgress and onError can
report headless work to app-owned logging or persistence; do not depend on screen
state to observe the result.
Receive HealthKit updates
AppleHealthSync can enable HealthKit observer delivery for each mapped sample type:
import { HKUpdateFrequency } from "@kingstinct/react-native-healthkit";
import { DataSync } from "@ovok/native/data-sync";
<DataSync.AppleHealthAuthorizationProvider readIdentifiers={healthKitTypes}>
<DataSync.AppleHealthSync
dataToSync={appleDataToSync}
backgroundDelivery={{ frequency: HKUpdateFrequency.hourly }}
onProgress={setProgress}
onError={reportHealthSyncError}
/>
</DataSync.AppleHealthAuthorizationProvider>;
Enable healthKit: { background: true } in the Expo config plugin, provide the
HealthKit usage descriptions, and rebuild. Keep AppleHealthSync mounted for as long
as the app should receive observer updates: unmounting it removes its subscriptions
and disables background delivery for the mapped identifiers. HealthKit and iOS
choose when to deliver an observer update; the frequency is a request, not a timer.
See the AppleHealthSync reference.
Optional: notify after BLE delivery
The BLE queue can schedule a local notification after backgroundSync.onResult
accepts a measurement:
backgroundSync: {
enabled: true,
storage: measurementStorage,
onResult: uploadMeasurement,
notifications: {
enabled: true,
title: "New measurement",
body: (result) => `${result.deviceData.name} reported a result`,
data: (result) => ({ resultId: result.id }),
},
}
Install and configure expo-notifications, request notification permission from a
foreground screen, and enable the notifications config plugin option. These are
local notifications; they do not start background work or provide remote push
tokens. The standalone permission and notification functions are listed in the
background-sync API reference.
Before shipping
- Use durable storage for queued BLE results and deduplicate uploads by
result.id. - Resolve the queue callback only after the server accepts the result.
- Build the native app with only the Bluetooth, HealthKit, Health Connect, service, and notification capabilities the app uses.
- Register the Health Connect headless task from the application entry point and restore its client, patient identity, and data mapping there.
- Keep HealthKit observer components mounted while observer delivery is needed.
- Explain foreground service notifications and any battery optimization request to users.