AppleHealthSync
A React component that manages the synchronization of health data from Apple HealthKit to a FHIR-compliant backend, featuring real-time change detection and explicit sync controls.
Overview
The AppleHealthSync component handles the complex process of reading health data from Apple HealthKit and synchronizing it with a FHIR backend. It provides real-time change detection, bounded retries for transient saves, and comprehensive progress tracking.
Features
- FHIR Integration: Converts HealthKit data to FHIR Observation resources
- Real-time Sync: Automatically syncs when new data is added to HealthKit
- Change Detection: Subscribes to HealthKit data changes and triggers sync
- 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 batched operations
- Background Delivery: Uses HealthKit observer delivery when
backgroundDeliveryis configured - Debounced Updates: Prevents excessive sync operations with intelligent debouncing
Basic Example
import { ObservationCode } from "@ovok/core";
import { AppleHealthSyncProps, DataSync } from "@ovok/native/data-sync";
import { HKQuantityTypeIdentifier, HKUpdateFrequency } from "@kingstinct/react-native-healthkit";
import React from "react";
const dataToSync: AppleHealthSyncProps["dataToSync"] = {
"body-temperature": {
typeIdentifiers: [
{
typeIdentifier: HKQuantityTypeIdentifier.bodyTemperature,
code: ObservationCode.BODY_TEMPERATURE,
},
],
minDate: new Date("2025-01-01"),
},
};
const HealthDataSync = () => {
return (
<DataSync.AppleHealthAuthorizationProvider
readIdentifiers={[HKQuantityTypeIdentifier.bodyTemperature]}
>
<DataSync.AppleHealthSync
dataToSync={dataToSync}
backgroundDelivery={{ frequency: HKUpdateFrequency.hourly }}
/>
</DataSync.AppleHealthAuthorizationProvider>
);
};
export default HealthDataSync;
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dataToSync | Record<HealthSyncKeys, { typeIdentifiers: TypeIdentifier[], minDate?: Date }> | required | Configuration of data types to sync with their identifiers |
chunkSize | number | 5000 | Number of samples to process in each sync batch |
wifiOnly | boolean | false | Pause imports on mobile data when enabled |
backgroundDelivery | { frequency?: HKUpdateFrequency } | undefined | Opt in to HealthKit observer delivery while the app is in the background |
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: HKQuantityTypeIdentifier; // HealthKit identifier
code: ObservationCode; // FHIR observation code
}[];
minDate?: Date; // Optional start date for sync
};
};
Change Detection Features
- Background Updates: Detects changes even when app is in background when
backgroundDeliveryis configured - Debounced Sync: Prevents excessive sync operations with 400ms debouncing
- Selective Sync: Only syncs the specific data types that changed
- Memory Efficient: Automatically manages subscriptions and cleanup
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
When paused, onProgress includes reason: "offline" or reason: "wifi-only".
Progress Tracking
The component provides detailed progress information:
interface SyncProgress {
measurementTypeKey: MeasurementTypeKey;
status: "started" | "completed" | "failed" | "idle" | "syncing" | "paused";
reason?: "offline" | "wifi-only";
progress?: {
done: number; // Samples processed
total: number; // Total samples to process
};
}
Progress States
idle: Sync not yet startedstarted: Sync initialization in progresssyncing: Actively processing HealthKit datapaused: Sync paused (network or system reasons)completed: All data successfully synced
Error Handling
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.
Handle various sync error scenarios:
Network-related Errors
const handleSyncError = (error: Error) => {
if (error.message.includes("network")) {
showNotification("Health sync could not save this measurement type");
}
};
HealthKit Permission Errors
const handleSyncError = (error: Error) => {
if (error.message.includes("authorization")) {
// Guide user to check HealthKit permissions
showHealthKitPermissionGuide();
}
};
Performance Considerations
- Change Subscriptions: Efficiently manages HealthKit change subscriptions
- Debounced Updates: Prevents excessive sync operations with 400ms debouncing
- Chunked Processing: Processes large datasets in configurable chunks
- Memory Management: Automatically cleans up subscriptions and resources
- Network Optimization: Syncs on any connection by default; set
wifiOnlyto preserve mobile data - Background Processing: HealthKit may wake the app when configured; process-death persistence and credentials remain app-owned
Background Sync Configuration
To enable background health data sync, configure your app properly:
// app.config.ts
plugins: [
[
"@kingstinct/react-native-healthkit",
{
background: true, // Enable background processing
NSHealthShareUsageDescription: "Background health data sync",
NSHealthUpdateUsageDescription: "Update health data in background",
},
],
];
Then opt in at runtime:
<DataSync.AppleHealthSync
dataToSync={dataToSync}
backgroundDelivery={{ frequency: HKUpdateFrequency.hourly }}
wifiOnly={false}
/>
backgroundDelivery enables HealthKit delivery for every configured type and removes those
registrations when the component unmounts or the prop is removed. HealthKit may wake the app while
the device is locked, but the import waits until HealthKit permits reads after unlock. Test this on
a physical device.
When AppleHealthAuthorizationProvider is present, AppleHealthSync subscribes only to read
identifiers that HealthKit has answered for. This means an optional type left off the HealthKit
prompt is skipped instead of creating an Authorization not determined observer error. The
provider still reports unanswered identifiers so the app can explain or request them explicitly.
Platform Requirements
- iOS Only: Exclusive to iOS devices
- HealthKit: Requires device with HealthKit support
- iOS Version: Requires iOS 14.0+ for optimal functionality
- Network: Requires internet connectivity for FHIR backend
- Permissions: Requires appropriate HealthKit permissions
Dependencies
@ovok/core: FHIR client and patient management@kingstinct/react-native-healthkit: iOS HealthKit integration@react-native-community/netinfo: Network status monitoring
Related Components
AppleHealthAuthorizationProvider: Permission managementSyncProgressList: UI component for displaying sync progressAndroid Health Sync: Android equivalent component