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
- A native development build from step 1.
- An authenticated Patient profile from step 3.
- The Native health data guide and DataSync reference.
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:
| Data | Observation coding | SDK behavior | How to present it |
|---|---|---|---|
| Apple sleep | urn:ovok:healthkit:sample-type + HKCategoryTypeIdentifierSleepAnalysis | session; HealthKit category intervals can include in-bed, awake and asleep states | Show a sleep-session interval. Do not label its elapsed minutes as time asleep. |
| Health Connect sleep | urn:ovok:health-connect:record-type + SleepSession | session; the source record supplies an interval and may contain stage records | Show the source interval and any available stages without inventing missing stages. |
| Apple steps | urn:ovok:healthkit:sample-type + HKQuantityTypeIdentifierStepCount | daily-total; grouped by the local calendar day | Label as a local-day step total. |
| Health Connect steps | urn:ovok:health-connect:record-type + Steps | daily-total; grouped by the local calendar day | Label as a local-day step total. |
| Resting heart rate | http://loinc.org + 40443-4 | point | Show 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.
HealthImportCardis 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
| Symptom | Check |
|---|---|
| Permission dialog does not appear | The provider is inside the authenticated app, and the native build includes the SDK plugin configuration. |
| Import appears successful but list is empty | Confirm the member has source records, read permission and an active Patient profile. |
| The app still requests write access | Remove write identifiers and Health Connect write permissions. |
| HealthKit integration is missing after config changes | Re-run Prebuild and rebuild the native app; Expo Go cannot supply the integration. |
| Sleep duration appears to mean time asleep | Present the source interval and stages; do not describe elapsed interval duration as time asleep. |
| Step totals shift at daylight-saving changes | Keep the SDK's local-calendar-day semantics; do not convert them to a 24-hour rate. |