Skip to content

feat(app): export glucose readings to Apple Health (HealthKit) - #10

Open
shroominic wants to merge 1 commit into
feat/sim-demo-modefrom
feat/healthkit-export
Open

feat(app): export glucose readings to Apple Health (HealthKit)#10
shroominic wants to merge 1 commit into
feat/sim-demo-modefrom
feat/healthkit-export

Conversation

@shroominic

Copy link
Copy Markdown
Owner

Summary

Adds an opt-in Apple Health export: the app writes its CgmReadings to HealthKit as HKQuantityTypeIdentifierBloodGlucose samples (mg/dL). Wellness / self-experimentation framing — no medical claims, no interpretation, nothing leaves the device unless the user turns it on.

Stacked on #4 (feat/sim-demo-mode) — please merge that first. This branch was cut from feat/sim-demo-mode and targets it as base.

Approach: package vs platform channel

Used the maintained health package (^13.3.1) rather than a hand-rolled platform channel. It is the idiomatic, lowest-risk path — it ships the native HealthKit bridge, authorization flow, and unit handling, so the Dart side stays thin and there's no Swift to maintain. All HealthKit calls are guarded by Platform.isIOS; on every other platform the service short-circuits (the Integrations card shows "only available on iOS"), so Android is unaffected at runtime.

What's included

  • lib/src/healthkit_export.dart
    • HealthKitExportService (implements a small GlucoseExporter interface for testability): configure → request authorization → write blood-glucose samples.
    • HealthExportController (ChangeNotifier): owns the opt-in + last-synced state, persisted in SharedPreferences. Incremental syncs use a recorded-time watermark so repeated "Sync now" taps don't duplicate samples.
  • lib/src/integrations_settings_pane.dart — new Integrations tab in the settings sheet: "Export to Apple Health" toggle, "Sync now", reading count, last-synced state. The toggle triggers the HealthKit auth sheet; the actual write is gated behind opt-in. Diff to the shared settings widget (main.dart) is minimal and clearly delimited (--- BEGIN/END Integrations tab ---); the Developer tab is untouched.
  • Teststest/healthkit_export_test.dart covers opt-in persistence, declined authorization, incremental sync watermarking, and the unsupported-platform path via an injected fake exporter.

iOS native changes (entitlement + plist)

  • ios/Runner/Runner.entitlements (new) — com.apple.developer.healthkit = true, wired via CODE_SIGN_ENTITLEMENTS on all three Runner build configs (Debug/Release/Profile).
  • ios/Runner/Info.plistNSHealthUpdateUsageDescription and NSHealthShareUsageDescription strings.
  • iOS deployment target 13 → 14 (health plugin minimum; the Live Activity feature already required 16.1).

Android

  • minSdk bumped to 26 (Health Connect minimum pulled in by the plugin). No Android Health permissions are requested at runtime since the Dart service is iOS-gated.

Verification (iPhone 15 Pro Max simulator)

  • flutter analyze clean (only a pre-existing lint in live_activity_payload.dart).
  • flutter test — all 23 pass.
  • flutter build ios --simulator --debug clean; flutter build apk --debug clean.
  • Ran with --dart-define=OG_DEMO=true, opened Settings → Integrations, and triggered the toggle. The native HealthKit authorization sheet appears requesting Blood Glucose write access (showing the Info.plist usage description), and the auth + export code path runs with no errors in the logs.

Screenshots captured locally (not embeddable in a gh PR body):

  • Integrations tab UI: /tmp/og-hk-integrations-pane.png
  • HealthKit permission sheet: /tmp/og-healthkit.png

iOS signing caveat for a real device

The simulator HealthKit store accepts the auth/write path without a provisioning profile. For a real device, the HealthKit capability must be enabled on the App ID in the Apple Developer portal and included in the provisioning profile that signs Runner (DEVELOPMENT_TEAM is already YLK778Z528). This is a deploy-time signing step — no code change needed.

🤖 Generated with Claude Code

Add an opt-in Apple Health integration that writes the app's CgmReadings
to HealthKit as HKQuantityTypeIdentifierBloodGlucose samples (mg/dL).

- HealthKitExportService wraps the `health` package: configures, requests
  authorization, and writes blood-glucose samples. iOS-only (Platform.isIOS);
  short-circuits everywhere else so Android is unaffected.
- HealthExportController owns the opt-in + last-synced state (persisted via
  SharedPreferences) and runs incremental syncs using a recorded-time
  watermark so repeated taps don't duplicate samples.
- New "Integrations" tab in the settings sheet with an "Export to Apple
  Health" toggle, "Sync now", reading count, and last-synced state. Writes
  are gated behind explicit user opt-in. Wellness framing, no medical claims.
- iOS: add HealthKit entitlement (Runner.entitlements + CODE_SIGN_ENTITLEMENTS
  on all Runner configs) and NSHealth{Share,Update}UsageDescription strings.
  Bump iOS deployment target 13 -> 14 (health plugin minimum).
- Android: bump minSdk to 26 (Health Connect minimum pulled in by the plugin).

Tests: unit coverage for opt-in/auth-decline/incremental-sync/unsupported via
an injected fake exporter. flutter analyze clean; flutter test green; iOS
simulator + Android debug builds pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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