Skip to content

Fix duplicate iOS sleep-stage queries - #21

Merged
dmarttila merged 3 commits into
steps-sensor-hc-mergefrom
codex/unified-ios-sleep-query
Aug 26, 2026
Merged

dmarttila merged 3 commits into
steps-sensor-hc-mergefrom
codex/unified-ios-sleep-query

Conversation

@dmarttila

Copy link
Copy Markdown

Summary

  • query HealthKit's sleep-analysis sample type once per date range instead of once per Dart alias
  • map each native sleep category to one canonical requested HealthDataType while retaining legacy asleep-unspecified records
  • preserve HealthKit sample UUID and native device identifier for reliable de-duplication and provenance
  • consistently expose Apple-specific sleep-stage values as durations in minutes

Why

On iOS, SLEEP_DEEP and SLEEP_ASLEEP_DEEP both filter the same HealthKit category value, as do SLEEP_REM and SLEEP_ASLEEP_REM. SLEEP_ASLEEP also overlaps SLEEP_ASLEEP_CORE for Core records. Asking for the full iOS sleep set therefore returned the same native samples under multiple Dart types.

In a diagnostic export containing 951 sleep rows, all 80 Deep records, all 110 REM records, and 243 Core records were duplicated. There were only 518 unique time intervals, all from the same Apple Watch source.

The Apple-specific aliases also retained raw category values 3, 4, and 5 while declaring MINUTE as their unit. This change converts them from timestamps just like the generic sleep types.

Compatibility

  • iOS callers can continue requesting the existing generic and Apple-specific type list.
  • If both aliases are requested, Apple-specific Core/Deep/REM types are preferred and returned once.
  • If only a generic alias is requested, the plugin returns that generic type.
  • SLEEP_ASLEEP continues to include asleep-unspecified, and represents Core when SLEEP_ASLEEP_CORE was not requested.
  • Android query behavior is unchanged.
  • Samples without UUIDs retain the existing structural de-duplication behavior.

Verification

  • flutter test test/health_test.dart: 4 tests passed
  • xcrun swiftc -parse packages/health/ios/Classes/SwiftHealthPlugin.swift
  • flutter build ios --simulator --no-codesign: succeeded
  • git diff --check

The package-wide analyzer still reports its existing example-app info-level lint backlog; no new diagnostic points to the changed files.

…leepData

Sleep types are now always routed through getSleepData/_dataSleepQuery
on iOS, so the SLEEP_* filter branches in getData() were unreachable
and duplicated the category-value mapping already encoded in
_requestedIosSleepType. Removed to avoid the two copies drifting.

getSleepData() was writing the literal string "unknown" when a sample
had no HKDevice, which defeats health_data_point.dart's null-coalescing
fallback to Health().deviceId (that fallback only triggers when the key
is absent/null). Now omits source_device_id entirely when there's no
device, matching the pattern used elsewhere in this file.
@dmarttila
dmarttila merged commit 319f903 into steps-sensor-hc-merge Aug 26, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant