Skip to main content

4. Connect Apple Health and Health Connect

What we are building​

An optional, member-controlled connection to Apple HealthKit on iOS or Health Connect on Android. This step imports sleep-session records, daily step totals and resting-heart-rate samples using the current Native SDK data-sync APIs.

What you should already have​

The implementation​

Use the SDK's built-in session mode for sleep, daily-total for steps and the catalog mapping for resting heart rate. Leave code and system unset: the current SDK catalog supplies the platform code/system for sleep and steps, and the reviewed LOINC mapping for resting heart rate.

import { MeasurementTypeKey } from "@ovok/core";
import {
HealthDataType,
type AppleHealthSyncProps,
type AndroidHealthSyncProps,
} from "@ovok/native/data-sync";
import {
HKCategoryTypeIdentifier,
HKQuantityTypeIdentifier,
} from "@kingstinct/react-native-healthkit";

const minDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

export const appleHealthDataToSync: AppleHealthSyncProps["dataToSync"] = {
[HealthDataType.sleep]: {
typeIdentifiers: [
{ typeIdentifier: HKCategoryTypeIdentifier.sleepAnalysis },
],
mode: "session",
minDate,
},
[MeasurementTypeKey.stepCount]: {
typeIdentifiers: [
{ typeIdentifier: HKQuantityTypeIdentifier.stepCount },
],
mode: "daily-total",
minDate,
},
[MeasurementTypeKey.restingHeartRate]: {
typeIdentifiers: [
{ typeIdentifier: HKQuantityTypeIdentifier.restingHeartRate },
],
minDate,
},
};

export const healthConnectDataToSync: AndroidHealthSyncProps["dataToSync"] = {
[HealthDataType.sleep]: {
typeIdentifiers: [{ typeIdentifier: "SleepSession" }],
mode: "session",
minDate,
},
[MeasurementTypeKey.stepCount]: {
typeIdentifiers: [{ typeIdentifier: "Steps" }],
mode: "daily-total",
minDate,
},
[MeasurementTypeKey.restingHeartRate]: {
typeIdentifiers: [{ typeIdentifier: "RestingHeartRate" }],
minDate,
},
};

Render the platform-specific provider only inside the authenticated patient area and after the current Patient has loaded. Explain each requested data type before asking for access.

import { ActivityIndicator, Button, Platform } from "react-native";
import { DataSync } from "@ovok/native/data-sync";
import {
HKCategoryTypeIdentifier,
HKQuantityTypeIdentifier,
} from "@kingstinct/react-native-healthkit";

const appleReadIdentifiers = [
HKCategoryTypeIdentifier.sleepAnalysis,
HKQuantityTypeIdentifier.stepCount,
HKQuantityTypeIdentifier.restingHeartRate,
];

const healthConnectReadIdentifiers = [
"SleepSession",
"Steps",
"RestingHeartRate",
];

function AppleHealthImport() {
return (
<DataSync.AppleHealthAuthorizationProvider
readIdentifiers={appleReadIdentifiers}
skipRequest={false}
renderManualRequestUI={(requestAccess) => (
<Button title="Allow Apple Health access" onPress={requestAccess} />
)}
fallback={<ActivityIndicator />}
>
<DataSync.AppleHealthSync
dataToSync={appleHealthDataToSync}
onError={(error) => console.error("HealthKit import failed", error)}
/>
</DataSync.AppleHealthAuthorizationProvider>
);
}

function AndroidHealthImport() {
return (
<DataSync.AndroidHealthConnectAuthorizationProvider
readIdentifiers={healthConnectReadIdentifiers}
skipRequest={false}
renderManualRequestUI={(requestAccess) => (
<Button title="Allow Health Connect access" onPress={requestAccess} />
)}
fallback={<ActivityIndicator />}
>
<DataSync.AndroidHealthSync
dataToSync={healthConnectDataToSync}
onError={(error) => console.error("Health Connect import failed", error)}
/>
</DataSync.AndroidHealthConnectAuthorizationProvider>
);
}

export function OptionalHealthImport({ enabled }: { enabled: boolean }) {
if (!enabled) return null;
return Platform.OS === "ios" ? <AppleHealthImport /> : <AndroidHealthImport />;
}

The sync components use the active Ovok client and Patient context. Keep them unmounted until authentication and profile lookup are complete. Add SyncProgressList if the screen needs a progress view; the health data guide documents its current progress contract.

Keep the source semantics intact​

The SDK's catalog preserves the platform coding for sleep and step records:

DataObservation codingSDK behaviorHow to present it
Apple sleepurn:ovok:healthkit:sample-type + HKCategoryTypeIdentifierSleepAnalysissession; HealthKit category intervals can include in-bed, awake and asleep statesShow a sleep-session interval. Do not label its elapsed minutes as time asleep.
Health Connect sleepurn:ovok:health-connect:record-type + SleepSessionsession; the source record supplies an interval and may contain stage recordsShow the source interval and any available stages without inventing missing stages.
Apple stepsurn:ovok:healthkit:sample-type + HKQuantityTypeIdentifierStepCountdaily-total; grouped by the local calendar dayLabel as a local-day step total.
Health Connect stepsurn:ovok:health-connect:record-type + Stepsdaily-total; grouped by the local calendar dayLabel as a local-day step total.
Resting heart ratehttp://loinc.org + 40443-4pointShow as a measurement with its timestamp; do not derive readiness, recovery or stress.

Sleep and step observations are not LOINC-coded by this mapping. In particular, do not substitute LOINC 41950-7 for local-day step totals: that code represents a 24-hour rate, while a local calendar day can be shorter or longer at daylight-saving transitions. HealthKit sleep category values are also not a single “time asleep” measurement.

Important Ovok decisions​

  • Request read permission only for the three records used above. Do not request write access.
  • HealthKit does not reveal whether a person denied read access to a specific type. Empty results can be a valid state.
  • The member can skip the connection. Refusal or unavailable platform services must not block reflections or goals.
  • HealthImportCard is an adapter-driven UI primitive; it is not the FHIR-backed HealthKit / Health Connect importer. Use the DataSync providers for this flow.
  • Use the background sync guide only after foreground import works and the product needs it. Background delivery is platform-scheduled, not a promise of immediate sync.

Expected result​

When records and permissions are available, sleep sessions, local-day step totals and resting-heart-rate samples are saved as FHIR Observations in Ovok. The app keeps source coding and measurement semantics visible; sleep and steps are not presented as LOINC values.

Common errors and troubleshooting​

SymptomCheck
Permission dialog does not appearThe provider is inside the authenticated app, and the native build includes the SDK plugin configuration.
Import appears successful but list is emptyConfirm the member has source records, read permission and an active Patient profile.
The app still requests write accessRemove write identifiers and Health Connect write permissions.
HealthKit integration is missing after config changesRe-run Prebuild and rebuild the native app; Expo Go cannot supply the integration.
Sleep duration appears to mean time asleepPresent the source interval and stages; do not describe elapsed interval duration as time asleep.
Step totals shift at daylight-saving changesKeep the SDK's local-calendar-day semantics; do not convert them to a 24-hour rate.

Previous / next​