Skip to content

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

Open
oakleaf wants to merge 1 commit intokingstinct:masterfrom
appitudeio:feat/background-delivery-appdelegate
Open

feat: register observer queries in AppDelegate for background delivery#341
oakleaf wants to merge 1 commit intokingstinct:masterfrom
appitudeio:feat/background-delivery-appdelegate

Conversation

@oakleaf
Copy link
Copy Markdown

@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

⚠️ No Changeset found

Latest commit: 3fb1b3f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a 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: 3fb1b3f

…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
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

1 participant