Skip to content

feat: register observer queries in AppDelegate for background delivery#341

Merged
robertherber merged 2 commits intokingstinct:masterfrom
appitudeio:feat/background-delivery-appdelegate
Apr 8, 2026
Merged

feat: register observer queries in AppDelegate for background delivery#341
robertherber merged 2 commits intokingstinct:masterfrom
appitudeio:feat/background-delivery-appdelegate

Conversation

@oakleaf
Copy link
Copy Markdown
Contributor

@oakleaf oakleaf commented Apr 4, 2026

Summary

Fixes #51 — HealthKit background delivery silently fails when the app is terminated because observer queries are only registered in JS-land, but Apple requires them in didFinishLaunchingWithOptions before the JS bridge boots.

Changes

  • BackgroundDeliveryManager.swift (new) — Plain Swift singleton (not NitroModules) that:

    • Reads persisted type identifiers from UserDefaults at app launch
    • Registers HKObserverQuery + enableBackgroundDelivery for each type immediately
    • Uses nil predicate (not Date.init()) to catch samples written while the app was dead
    • Queues events until JS subscribes, then flushes them
    • Thread-safe via concurrent dispatch queue with barrier writes
  • CoreModule.swift — Two new JS-callable methods:

    • configureBackgroundTypes(types, frequency) — persists config to UserDefaults + registers observers for current session
    • clearBackgroundTypes() — clears persisted config + stops all observers
  • app.plugin.ts — Expo config plugin now injects BackgroundDeliveryManager.shared.setupBackgroundObservers() into didFinishLaunchingWithOptions via withAppDelegate

  • TypeScript — New exports configureBackgroundTypes and clearBackgroundTypes added to Nitro spec, iOS implementation, non-iOS stubs, and test setup

How it works

  1. App calls configureBackgroundTypes(["HKQuantityTypeIdentifierStepCount", ...], UpdateFrequency.immediate) from JS
  2. Types + frequency are persisted to UserDefaults
  3. On next cold launch, BackgroundDeliveryManager.setupBackgroundObservers() runs from AppDelegate — before JS bridge — reads UserDefaults and registers observer queries
  4. When HealthKit fires a background update, the observer handler queues the event
  5. Once JS boots and subscribes, queued events are flushed

Non-Expo / bare RN

For apps not using Expo config plugins, add this to AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    BackgroundDeliveryManager.shared.setupBackgroundObservers()
    // ... rest of setup
}

Design decisions

  • UserDefaults as bridge: Types must be known at didFinishLaunchingWithOptions time before JS runs. UserDefaults is the simplest persistence that survives app termination.
  • Plain Swift class: NitroModules aren't initialized at AppDelegate time, so this must be a standalone singleton.
  • nil predicate: The existing subscribeToObserverQuery uses Date.init() which misses data written while the app was dead. Background observers need to catch everything.
  • Separate from existing API: configureBackgroundTypes is additive — the existing enableBackgroundDelivery + subscribeToObserverQuery JS APIs continue to work unchanged for foreground use.

Test plan

  • Build with Expo, verify didFinishLaunchingWithOptions contains the setup call
  • Call configureBackgroundTypes from JS, verify UserDefaults populated
  • Kill app, write health data from another source, verify app wakes and events are delivered
  • Verify foreground subscribeToChanges still works as before
  • Verify clearBackgroundTypes stops observers and clears UserDefaults
  • Verify bare RN (non-Expo) setup with manual AppDelegate integration

🤖 Generated with Claude Code

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 4, 2026

🦋 Changeset detected

Latest commit: c7b2180

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 Patch

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 4, 2026

Open in StackBlitz

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

commit: c7b2180

…nd delivery

Apple requires HKObserverQuery to be set up in didFinishLaunchingWithOptions
before the JS bridge boots. Without this, background delivery silently fails
when iOS terminates and relaunches the app for a HealthKit update.

This adds BackgroundDeliveryManager — a plain Swift singleton (not NitroModules)
that reads persisted type identifiers from UserDefaults at app launch and
registers observer queries + enableBackgroundDelivery immediately. Events
that arrive before JS is ready are queued and flushed when JS subscribes.

New JS API:
- configureBackgroundTypes(types, frequency) — persist + register observers
- clearBackgroundTypes() — clear config + stop observers

Expo config plugin now injects BackgroundDeliveryManager.shared.setupBackgroundObservers()
into AppDelegate.didFinishLaunchingWithOptions automatically.

Fixes kingstinct#51

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oakleaf oakleaf force-pushed the feat/background-delivery-appdelegate branch from 302a12e to 3fb1b3f Compare April 4, 2026 14:45
Register observer queries in AppDelegate to enable background delivery.
@robertherber robertherber merged commit 390f784 into kingstinct:master Apr 8, 2026
8 checks passed
@oakleaf oakleaf deleted the feat/background-delivery-appdelegate branch April 12, 2026 12:47
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.

Set up observer queries in didFinishLaunchingWithOptions to align with Apples recommendations

2 participants