Skip to main content

Platform Differences

While react-native-healthx provides a unified API, there are important differences between iOS HealthKit and Android Health Connect that you should be aware of.

Feature Comparison

FeatureiOS (HealthKit)Android (Health Connect)
Read sleep data✅ Yes✅ Yes
Write sleep data❌ No✅ Yes
Delete sleep data❌ No✅ Yes
Sleep stages✅ Yes (iOS 16+)✅ Yes
Native aggregation❌ No (JS fallback)✅ Yes
Background sync❌ Not yet❌ Not yet

Sleep Stage Mapping

Different platforms use different sleep stage classifications. react-native-healthx normalizes these to a unified set:

Unified TypeiOS HealthKitAndroid Health Connect
UNKNOWNUNKNOWNUNKNOWN (0)
AWAKEAWAKEAWAKE (1)
IN_BEDINBED- (not available)
SLEEPINGASLEEPSLEEPING (2)
LIGHTCORELIGHT (4)
DEEPDEEPDEEP (5)
REMREMREM (6)
OUT_OF_BED- (not available)OUT_OF_BED (3)

iOS Sleep Stages

On iOS 15 and earlier, only basic sleep stages are available:

  • IN_BED - User marked as in bed
  • SLEEPING - Generic asleep state
  • AWAKE - Awake during sleep period

iOS 16+ adds detailed stages:

  • LIGHT (called "Core" in HealthKit)
  • DEEP
  • REM

Android Sleep Stages

Health Connect provides all detailed stages from the start, including OUT_OF_BED which iOS doesn't track.

Write Limitations

iOS

iOS HealthKit does not allow third-party apps to write sleep analysis data. This is an Apple limitation, not a library limitation.

// This will throw on iOS
try {
await HealthX.sleep.writeSession({ ... });
} catch (error) {
console.log(error.message);
// "Writing sleep data is not supported on iOS..."
}

Always check before writing:

if (HealthX.supportsWrite()) {
await HealthX.sleep.writeSession({ ... });
}

Android

Full read/write support is available on Android with Health Connect.

Aggregation Implementation

iOS

On iOS, aggregation is calculated in JavaScript by fetching all samples and computing statistics client-side. This means:

  • All data must be fetched from HealthKit first
  • Large date ranges may be slower
  • Stage breakdown is calculated from individual samples

Android

Health Connect has native aggregation support, which is more efficient:

  • Uses aggregateRecord() API
  • Only returns computed results, not raw data
  • Better performance for large datasets

Permission Behavior

iOS

  • Permission dialog shows read/write options
  • Users can grant partial permissions
  • You cannot check if read permission was denied - iOS doesn't expose this
  • Permission changes require the user to go to the Health app

Android

  • Granular permission dialog with checkboxes
  • Clear indication of granted/denied status
  • Users manage permissions in Health Connect app
  • Revoked permissions take effect immediately

Data Source Tracking

iOS

Sleep data includes source information:

  • sourceName: Human-readable name (e.g., "Apple Watch")
  • sourceId: Bundle identifier (e.g., "com.apple.health")

Android

Health Connect tracks data origins:

  • dataOrigin: Package name of the app that wrote the data

Handling Platform Differences

Conditional Features

function SleepTracker() {
const canWrite = HealthX.supportsWrite();
const platform = HealthX.getPlatform();

return (
<View>
<SleepHistory />

{canWrite && (
<Button
title="Log Sleep Manually"
onPress={handleManualLog}
/>
)}

{platform === 'ios' && (
<Text>
Tip: Use Apple Watch for automatic sleep tracking
</Text>
)}
</View>
);
}

Platform-Specific Messaging

function getHealthAppName() {
return HealthX.getPlatform() === 'ios'
? 'Health app'
: 'Health Connect app';
}

function showPermissionDeniedMessage() {
const appName = getHealthAppName();
Alert.alert(
'Permission Required',
`Please enable health data access in the ${appName}.`,
[
{ text: 'Cancel' },
{ text: 'Open Settings', onPress: () => HealthX.openHealthSettings() }
]
);
}

Handling Missing Stages

function getSleepBreakdown(session: SleepSession) {
// Some stages might not be available on all platforms/devices
const stages = {
light: 0,
deep: 0,
rem: 0,
awake: 0,
};

for (const stage of session.stages) {
switch (stage.type) {
case 'LIGHT':
case 'SLEEPING': // Generic sleep counts as light
stages.light += stage.duration;
break;
case 'DEEP':
stages.deep += stage.duration;
break;
case 'REM':
stages.rem += stage.duration;
break;
case 'AWAKE':
stages.awake += stage.duration;
break;
}
}

return stages;
}