AndroidHealthSync
A React component that manages the synchronization of health data from Android Health Connect to a FHIR-compliant backend, providing real-time progress tracking and explicit sync controls.
Overview
The AndroidHealthSync component handles the complex process of reading health data from Android Health Connect and synchronizing it with a FHIR backend. It provides bounded retries for transient saves and real-time progress updates.
Features
- FHIR Integration: Converts Health Connect data to FHIR Observation resources
- Wi-Fi-only Option: Pauses imports on mobile data when
wifiOnlyis enabled - Progress Tracking: Real-time progress updates for each measurement type
- Chunked Processing: Handles large datasets efficiently through chunked synchronization
- Error Handling: Retries transient saves and reports permanent failures
- Background Processing: The mounted component syncs in-process; process-death scheduling uses the documented headless task
- Automatic Cleanup: Stops new sync work on component unmount. An already scheduled retry is not cancelled by unmount and may still run; make the save adapter abortable if the host must cancel it.
Process-death background sync
AndroidHealthSync runs while its React component is mounted. To continue
after Android recreates the app process, register a headless task from the app
entry point and schedule the SDK's WorkManager job:
import {
registerAndroidHealthConnectBackgroundTask,
runAndroidHealthConnectSync,
scheduleAndroidHealthConnectSync,
} from "@ovok/native/background-sync";
registerAndroidHealthConnectBackgroundTask(async ({ reason }) => {
const { client, patientId, dataToSync } = await restoreHealthSyncState();
await runAndroidHealthConnectSync({
client,
patientId,
dataToSync,
});
});
await scheduleAndroidHealthConnectSync({ intervalMinutes: 15 });
The app must declare and request android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND
where supported. The task owns client/auth restoration because those values are
application-specific and are never serialized by the SDK.
Basic Example
import { ObservationCode } from "@ovok/core";
import { AndroidHealthSyncProps, DataSync } from "@ovok/native/data-sync";
import React from "react";
const dataToSync: AndroidHealthSyncProps["dataToSync"] = {
"body-temperature": {
typeIdentifiers: [
{
typeIdentifier: "BodyTemperature",
code: ObservationCode.BODY_TEMPERATURE,
},
],
minDate: new Date("2025-01-01"),
},
};
const HealthDataSync = () => {
// This component is wrapped in the AndroidHealthConnectAuthorizationProvider
// to handle the authorization process
return (
<DataSync.AndroidHealthConnectAuthorizationProvider
readIdentifiers={["BodyTemperature"]}
backgroundAccess
>
{/* You have to be authorized to sync the health data */}
<DataSync.AndroidHealthSync dataToSync={dataToSync} />
</DataSync.AndroidHealthConnectAuthorizationProvider>
);
};
export default HealthDataSync;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dataToSync | Partial<Record<HealthSyncKeys, { typeIdentifiers: { typeIdentifier: RecordType, code: ObservationCode }[], minDate?: Date }>> | required | Configuration of supported health data types and their FHIR codes |
chunkSize | number | 5000 | Number of samples to process in each sync batch |
wifiOnly | boolean | false | Pause imports on mobile data when enabled |
onError | (error: Bundle<Resource> | Error | null) => void | undefined | Callback for sync errors |
onProgress | (progress: Partial<Record<MeasurementTypeKey, SyncProgress>>) => void | undefined | Callback for real-time progress updates |
Data Configuration
The dataToSync prop defines which health data types to synchronize:
type DataToSync = {
[key in HealthSyncKeys]?: {
typeIdentifiers: {
typeIdentifier: RecordType; // Health Connect record type
code: ObservationCode; // FHIR observation code
}[];
minDate?: Date; // Optional start date for sync
};
};
Network Behavior
The component applies only the configured network policy:
- Any Connection: Full sync operations are enabled by default
- Mobile Data: Sync operations pause only when
wifiOnlyis enabled - No Connection: All sync operations paused
- Retryable Saves: Network errors, HTTP 429, and HTTP 5xx saves retry after 1s, 4s, and 16s
Retries are at-least-once. If the request reaches the server but the response is lost, the retry can create a duplicate observation; use a server-side idempotency key or application-level deduplication for production imports.
When paused, onProgress includes reason: "offline" or reason: "wifi-only".
Progress Tracking
The component provides detailed progress information through the onProgress callback:
interface SyncProgress {
measurementTypeKey: MeasurementTypeKey;
status: "started" | "completed" | "failed" | "idle" | "syncing" | "paused";
reason?: "offline" | "wifi-only";
progress?: {
done: number; // Number of samples processed
total: number; // Total samples to process
};
}
Progress States
idle: Sync not yet startedstarted: Sync initialization in progresssyncing: Actively processing datapaused: Sync paused (usually due to network conditions)completed: All data successfully synced
Error Handling
The component handles various error scenarios:
Network Errors
import type { Bundle, Resource } from "@medplum/fhirtypes";
const handleSyncError = (error: Bundle<Resource> | Error | null) => {
if (error instanceof Error && error.message.includes("network")) {
showNotification("Health sync could not save this measurement type");
}
};
Performance Considerations
HealthSyncKeys is the health-import subset of MeasurementTypeKey; it excludes
photoUpload and symptomQuestionnaire. A failed progress status means that
measurement type stopped after a non-retryable or exhausted save error.
- Chunked Processing: Processes data in configurable chunks to prevent memory issues
- Background Processing: Use the exported headless task and WorkManager helpers after Android recreates the process
- Network Optimization: Syncs on any connection by default; set
wifiOnlyto preserve mobile data - Memory Management: Automatically cleans up resources on component unmount
- Debounced Updates: Progress updates are debounced to prevent excessive re-renders
Platform Requirements
- Android Only: Exclusive to Android devices
- Health Connect: Requires Android Health Connect app
- Network: Requires internet connectivity for FHIR backend
- Permissions: Requires appropriate Health Connect permissions
Dependencies
@ovok/core: FHIR client and patient managementreact-native-health-connect: Android Health Connect integration@react-native-community/netinfo: Network status monitoring
Related Components
AndroidHealthConnectAuthorizationProvider: Permission managementAppleHealthSync: iOS equivalent componentSyncProgressList: UI component for displaying sync progress