Skip to main content

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 wifiOnly is 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 backgroundDelivery is 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​

PropTypeDefaultDescription
dataToSyncRecord<HealthSyncKeys, { typeIdentifiers: TypeIdentifier[], minDate?: Date }>requiredConfiguration of data types to sync with their identifiers
chunkSizenumber5000Number of samples to process in each sync batch
wifiOnlybooleanfalsePause imports on mobile data when enabled
backgroundDelivery{ frequency?: HKUpdateFrequency }undefinedOpt in to HealthKit observer delivery while the app is in the background
onError(error: Bundle<Resource> | Error | null) => voidundefinedCallback for sync errors
onProgress(progress: Partial<Record<MeasurementTypeKey, SyncProgress>>) => voidundefinedCallback 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 backgroundDelivery is 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 wifiOnly is 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 started
  • started: Sync initialization in progress
  • syncing: Actively processing HealthKit data
  • paused: 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:

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 wifiOnly to 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