Skip to main content

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…UseReference
Deliver Bluetooth readings through a durable queueBTProvider.backgroundSyncQueue Bluetooth readings
Keep a Bluetooth scan active on AndroidBTProvider's foreground service, or AndroidBluetoothForegroundService when you own the scanRun an Android BLE foreground service
Import Health Connect data after Android stops the processWorkManager plus a registered headless taskSchedule Health Connect imports
Import HealthKit updates while the app is backgroundedAppleHealthSync.backgroundDeliveryReceive 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.