Skip to content

feat: native background sync engine for HealthKit#343

Closed
oakleaf wants to merge 1 commit intokingstinct:masterfrom
appitudeio:feat/native-background-sync
Closed

feat: native background sync engine for HealthKit#343
oakleaf wants to merge 1 commit intokingstinct:masterfrom
appitudeio:feat/native-background-sync

Conversation

@oakleaf
Copy link
Copy Markdown
Contributor

@oakleaf oakleaf commented Apr 12, 2026

Summary

Adds configureBackgroundSync — a fully-native background sync pipeline that runs when HealthKit observer queries fire without the JS bridge available (e.g. after the app has been terminated by iOS).

The library stays consumer-agnostic: you provide the HTTP endpoint (url, method, headers), a list of SyncTypeConfig entries mapping HealthKit identifiers to your own type names and units, and an UpdateFrequency. Records are sent as a { records: [...] } JSON body with fields the consumer defined.

Why

configureBackgroundTypes (merged via #341) solves observer registration at AppDelegate time, but the delivery callback queues events until JS subscribes via subscribeToChanges. For apps that want to forward health data to a backend when the app is terminated, JS never gets a chance to run. configureBackgroundSync closes that gap by doing the sync entirely in native Swift.

How it works

  • Cumulative types (steps, distance, calories, active energy, flights, exercise minutes) use HKStatisticsCollectionQuery with .cumulativeSum. Apple deduplicates across iPhone + Watch sources and returns one correct total per day.
  • Discrete types (heart rate, weight, HRV, etc.) use HKSampleQuery, one record per sample.
  • Records are sent to a caller-configured HTTP endpoint with caller-configured headers — no assumptions about auth, body envelope, or backend shape.

Design choices

  • New native file ios/NativeSyncEngine.swift added to the existing companion pod ReactNativeHealthkitBackground — no NitroModules / C++ dependencies, safe to import from AppDelegate on any RN version.
  • Setup-time validation — URL well-formedness, HTTP method (POST/PUT/PATCH), non-empty typeConfigs, JSON encodability. Errors surface at configureBackgroundSync() call time rather than failing silently on every background wake.
  • 20-second hard timeout per sync event (iOS allows ~30s of background execution; 10s buffer for safety).
  • No retry/queue on HTTP failure — if the POST fails, the event is dropped. iOS fires observers again on the next HealthKit change, so backfill happens naturally via subsequent events. Documented prominently in README.
  • Content-Type defaults to application/json when not supplied in headers.
  • Today-only query window — HealthKit + iOS already handle offline/catch-up via observer queuing. For multi-day backfill, use the foreground APIs.

New TypeScript surface

  • configureBackgroundSync(endpoint, typeConfigs, updateFrequency): Promise<boolean>
  • clearBackgroundSync(): Promise<boolean>
  • BackgroundSyncEndpoint / SyncTypeConfig types

Docs

Added a ## Background Delivery & Native Sync section to the README with:

  • Comparison table between configureBackgroundTypes (observer + JS callback) and configureBackgroundSync (native HTTP)
  • Full usage example
  • Output record field table (type, value, unit, startTime, endTime, recordId, frequency, workoutActivityType, duration)
  • Behavior notes (no retry, today-only, 20s budget)

Test plan

  • End-to-end tested on iPhone with a consumer backend — steps / distance / calories arriving in the expected format, values matching Apple Health exactly.
  • Force-quit behavior verified — HealthKit daemon still relaunches the app for background delivery (Apple design; not subject to normal iOS force-quit rules).
  • Background delivery tested over multiple hours without the app being opened.

🤖 Generated with Claude Code

Adds `configureBackgroundSync` — a fully-native background sync pipeline that
runs when HealthKit observer queries fire without the JS bridge available
(e.g. after the app has been terminated by iOS).

The library is consumer-agnostic: you provide the HTTP endpoint (url, method,
headers), a list of SyncTypeConfig entries mapping HealthKit identifiers to
your own type names and units, and an UpdateFrequency. Records are sent as
a `{ records: [...] }` JSON body with fields the consumer defined.

Cumulative types (steps, distance, etc.) use HKStatisticsCollectionQuery with
.cumulativeSum — Apple deduplicates across iPhone + Watch and returns one
correct daily total per bucket. Discrete types use HKSampleQuery.

Implementation notes:
- New native file NativeSyncEngine.swift added to the existing companion
  ReactNativeHealthkitBackground pod (no NitroModules / C++ deps, safe for
  AppDelegate imports on any RN version).
- Setup-time validation: URL well-formedness, HTTP method (POST/PUT/PATCH),
  non-empty typeConfigs, JSON encodability. Errors surface at
  configureBackgroundSync() call time rather than failing silently on every
  background wake.
- 20-second hard timeout per sync event (iOS allows ~30s; 10s buffer for safety).
- No retry / queue on HTTP failure — relies on iOS to fire observers again
  on the next HealthKit change.
- Content-Type defaults to application/json when not supplied.

See README "Background Delivery & Native Sync" for comparison with the
existing configureBackgroundTypes observer-only API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 12, 2026

🦋 Changeset detected

Latest commit: e82a0c6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@kingstinct/react-native-healthkit Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 12, 2026

Open in StackBlitz

npm i https://pkg.pr.new/kingstinct/react-native-healthkit/@kingstinct/react-native-healthkit@343

commit: e82a0c6

@robertherber
Copy link
Copy Markdown
Member

Love this idea @oakleaf! But would prefer if we exposed all available data, which I don't think is the case here - right?

@oakleaf
Copy link
Copy Markdown
Contributor Author

oakleaf commented Apr 14, 2026

Let me work through that!

@oakleaf
Copy link
Copy Markdown
Contributor Author

oakleaf commented Apr 16, 2026

Superseded by #344 (v2 rewrite with four sample kinds, metadata enrichment, dual-unit hkUnit).

@oakleaf oakleaf closed this Apr 16, 2026
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.

2 participants