@appeeky/expo-healthkit

中文文档
0.4.2发布于 2026/10/27SEE LICENSE IN LICENSETypeScriptCommonJS零依赖构建来源已验证近 30 天下载 5097

Apple HealthKit and Android Health Connect for Expo. One identifier-based JavaScript API on iOS and Android.

原始使用文档
文档目录25 节
  1. 📋 Requirements
  2. 📦 Install
  3. ⚡️ Quick start
  4. 📚 API
  5. 🔐 Availability and permissions
  6. 📊 Samples and statistics
  7. ✍️ Write / delete
  8. 👤 Characteristics
  9. 🔔 Background updates
  10. 🏷️ Identifiers
  11. 🌍 Cross-platform
  12. What works on both
  13. iOS-only
  14. Behavioral differences
  15. 🔌 Config plugin
  16. 📱 Example app
  17. 🤖 Agent skills
  18. 🚀 Releases
  19. 🤝 Contributing
  20. Checks
  21. What to test
  22. Native rebuild
  23. Docs
  24. 🔒 Privacy
  25. 📄 License
image

@appeeky/expo-healthkit

Apple HealthKit and Android Health Connect for Expo and React Native, built on the Expo Modules API.

One JavaScript API on both platforms. You pass Apple HealthKit type identifiers (HKQuantityTypeIdentifierStepCount, …), query or save samples, and the native layer talks to HealthKit on iOS and Health Connect on Android.

iOSAndroid
BackendHealthKitHealth Connect
App codeSame identifiers and methodsSame identifiers and methods
AvailabilityisAvailable() when HealthKit is presentisAvailable() when Health Connect is installed
  • Expo SDK 53+ / New Architecture (tested on SDK 57)
  • Promise-based TypeScript API
  • Config plugin for HealthKit entitlements and Health Connect permissions (minSdk 26)
  • iOS 16.4+, Android 8+ (API 26+). Web reports isAvailable() === false

Health data APIs cannot run in Expo Go. Use a development build (npx expo run:ios or npx expo run:android).

📋 Requirements

  • Expo SDK 53 or later and a development build (not Expo Go)
  • iOS 16.4+, physical iPhone or Simulator, with the HealthKit capability enabled on the App ID in the Apple Developer portal
  • Android 8+ with Health Connect (built-in on Android 14+)

📦 Install

npx expo install @appeeky/expo-healthkit

From GitHub, if you are tracking main before a release:

npx expo install github:appeeky/expo-healthkit

Add the config plugin in app.json / app.config.ts:

{
  "expo": {
    "plugins": [
      [
        "@appeeky/expo-healthkit",
        {
          "healthSharePermission": "Allow $(PRODUCT_NAME) to read your health data",
          "healthUpdatePermission": "Allow $(PRODUCT_NAME) to write health data",
          "isBackgroundDeliveryEnabled": true
        }
      ]
    ],
    "ios": {
      "bundleIdentifier": "com.example.app"
    }
  }
}

Then rebuild native code:

npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:android

Enable the HealthKit capability on the App ID in the Apple Developer portal. The plugin writes the entitlement into the generated Xcode project; the capability still has to be allowed for that bundle ID.

On Android, install Health Connect if the OS does not already include it (built-in on Android 14+). The plugin declares the Health Connect permissions (all mapped types by default; narrow with healthConnectPermissions) and raises minSdk to 26. The module's own manifest declares none, so a bare React Native app that does not run the config plugin must add the android.permission.health.* uses-permission entries it needs to AndroidManifest.xml itself.

⚡️ Quick start

import * as HealthKit from '@appeeky/expo-healthkit';

if (!HealthKit.isAvailable()) {
  return;
}

await HealthKit.requestAuthorization({
  toRead: [
    HealthKit.QuantityType.stepCount,
    HealthKit.QuantityType.heartRate,
    HealthKit.CategoryType.sleepAnalysis,
    HealthKit.WorkoutType.workout,
  ],
  toShare: [HealthKit.QuantityType.stepCount],
});

const today = new Date();
today.setHours(0, 0, 0, 0);

const steps = await HealthKit.queryStatistics({
  type: HealthKit.QuantityType.stepCount,
  unit: HealthKit.Unit.count,
  from: today,
  to: new Date(),
  options: HealthKit.StatisticsOption.cumulativeSum,
});

const heartRate = await HealthKit.queryQuantitySamples({
  type: HealthKit.QuantityType.heartRate,
  unit: HealthKit.Unit.countPerMinute,
  from: today,
  to: new Date(),
  limit: 20,
  ascending: false,
});

📚 API

🔐 Availability and permissions

MethodNotes
isAvailable()true on iOS when HealthKit is present, and on Android when Health Connect is installed
getSupportedTypes()Identifiers this platform can provide. Static, no prompt. iOS: what HealthKit resolves on this OS; Android: the Health Connect mapping; web: []
getUnsupportedTypes(identifiers)The subset of identifiers this platform cannot provide
requestAuthorization({ toRead, toShare, includeBackgroundRead })Shows the system permission sheet (HealthKit or Health Connect). includeBackgroundRead adds Health Connect's READ_HEALTH_DATA_IN_BACKGROUND (Android only; needs the plugin's isHealthConnectBackgroundReadEnabled)
getAuthorizationStatus(type)iOS: reliable for write types. Android: reflects granted Health Connect read or write access
getRequestStatusForAuthorization({ toRead, toShare })Whether you still need to prompt
getGrantedPermissions()Android: granted android.permission.health.* strings. iOS: identifiers this app may write — HealthKit does not disclose read grants
requestPermissions(permissions)Android: request raw android.permission.health.* strings, resolves with the granted subset. iOS: throws ERR_HEALTHKIT_UNSUPPORTED
revokeAllPermissions()Android: in-app "disconnect Health Connect". The revoke takes effect when the app process ends, and Android may end it right away; until then getGrantedPermissions() still lists the grants. iOS: throws ERR_HEALTHKIT_UNSUPPORTED (HealthKit has no revoke)

On iOS, read authorization is intentionally opaque: getAuthorizationStatus returning notDetermined / sharingDenied does not mean the user blocked reads.

ERR_HEALTHKIT_UNSUPPORTED is the iOS mirror of ERR_HEALTH_CONNECT_UNSUPPORTED: the method exists on both platforms so app code needs no Platform.OS fork, and the one backend that cannot do it says so.

📊 Samples and statistics

MethodUse for
❤️queryQuantitySamples({ type, unit, from, to, limit, ascending })Heart rate, weight, glucose, …
😴queryCategorySamples({ type, from, to, limit, ascending })Sleep, mindful sessions, …
🏃queryWorkouts({ from, to, limit, ascending, activityType })Workouts
🫀queryElectrocardiograms({ from, to, limit, ascending, includeVoltage })ECG samples. Voltage is omitted unless includeVoltage: true
🔴queryActivitySummaries({ from, to })Move / Exercise / Stand rings
🏥queryClinicalRecords({ type, from, to, limit, ascending })FHIR clinical records. Needs isClinicalDataEnabled
👂queryAudiograms({ from, to, limit, ascending })Hearing-test audiograms
📍queryWorkoutRoute({ workoutUUID })GPS locations for a workout
🩺queryCorrelations({ type, from, to, limit, ascending })Blood pressure and food correlations
💓queryHeartbeatSeries({ from, to, limit, ascending, includeBeats })Beat-to-beat series used for HRV
📈queryStatistics({ type, unit, from, to, options })Totals / min / max / average for a range
📅queryStatisticsCollection({ type, unit, from, to, interval, options })Daily (or hourly) buckets
🔄queryAnchored({ type, unit, from, to, limit, anchor })Incremental sync. Store anchor, pass it back while hasMore

limit defaults to HealthKit's unlimited query. Dates accept Date or ISO-8601 strings. Units are HealthKit unit strings (count, count/min, kcal, kg, m, %, …) also exported as Unit.

Source filters

Every query above (except activity summaries and workout routes) accepts sources and excludeSources. Both take iOS bundle identifiers / Android package names; 'self' in excludeSources is this app. Use it to skip your own writes when you sync:

const heartRate = await HealthKit.queryQuantitySamples({
  type: HealthKit.QuantityType.heartRate,
  unit: HealthKit.Unit.countPerMinute,
  from: today,
  limit: 100,
  excludeSources: ['self'],
});

const watchOnly = await HealthKit.queryWorkouts({
  sources: ['com.apple.health'],
});

The filters run natively, before limit, on both platforms: limit: 100 with excludeSources: ['self'] returns the 100 newest samples that other apps wrote, not 100 rows minus yours. On iOS they compile to an HKQuery.predicateForObjects(from:) predicate; on Android sources is the request's dataOriginFilter and excludeSources is applied to each page before the limit is counted. queryStatistics / queryStatisticsCollection on Android support sources only; excludeSources there throws ERR_HEALTH_CONNECT_UNSUPPORTED. queryAnchored filters added samples; deletions carry no source and are always returned.

Anchored sync

queryAnchored returns { added, deleted, anchor, hasMore }. Apply the page, persist anchor, and call again while hasMore is true. Treat added as upserts keyed by uuid: a sample can arrive twice.

let anchor = await loadAnchor(); // null on first sync
let hasMore = true;
while (hasMore) {
  const page = await HealthKit.queryAnchored({ type, unit, limit: 500, anchor });
  await applyPage(page.added, page.deleted);
  anchor = page.anchor;
  hasMore = page.hasMore;
  await saveAnchor(anchor);
}
  • The anchor is an opaque token, not a timestamp. On iOS it is an archived HKQueryAnchor; on Android it wraps a Health Connect changes token (plus a page token during the first sync). It is not portable between platforms, between apps, or from another library — an anchor saved by @kingstinct/react-native-healthkit or react-native-health-connect, or an ISO date from a timestamp-based cursor, is not a valid token here. Persist it per platform and per type, and when migrating from another cursor start with anchor: null and dedupe by uuid.
  • Android, first sync. With no anchor, the existing records are read in pages of limit records (a heart-rate record can expand to many samples; no limit reads everything in one call). The changes token is taken before the first page, so records written or deleted meanwhile arrive as changes afterwards — hasMore stays true until those are read.
  • Android, expired or rejected anchor. Health Connect keeps its change log for 30 days, so a token unused for longer expires. queryAnchored then throws ERR_HEALTH_CONNECT_ANCHOR_EXPIRED instead of returning a delta: deletions made during the gap are lost, so restart with anchor: null and reconcile your copy against the full read. Health Connect reports a token it does not recognise (for example one from another library) the same way. A malformed anchor throws ERR_INVALID_ANCHOR, and other Health Connect errors are thrown as-is. The restart's read is limited to the last 30 days unless the user granted READ_HEALTH_DATA_HISTORY (requested with every toRead).
  • iOS. HealthKit does not say whether more results remain, so hasMore is true whenever a page is full; the next call may come back empty. A string that is not base64 is treated as "no anchor" and runs the initial fetch again (only a base64 string that is not an HKQueryAnchor archive rejects).

ECG, activity rings, clinical records, audiograms, workout GPS routes, heartbeat series, and correlations are not quantity/category/workout samples. Request the matching identifier in toRead:

await HealthKit.requestAuthorization({
  toRead: [
    HealthKit.ElectrocardiogramType.electrocardiogram,
    HealthKit.ActivitySummaryType.activitySummary,
    HealthKit.ClinicalType.allergyRecord,
    HealthKit.AudiogramType.audiogram,
    HealthKit.WorkoutType.workout,
    HealthKit.SeriesType.workoutRoute,
    HealthKit.SeriesType.heartbeat,
    HealthKit.CorrelationType.bloodPressure,
    HealthKit.CorrelationType.food,
  ],
});

const rings = await HealthKit.queryActivitySummaries({ from: today });
const [workout] = await HealthKit.queryWorkouts({ limit: 1 });
const routes = workout
  ? await HealthKit.queryWorkoutRoute({ workoutUUID: workout.uuid })
  : [];
const pressure = await HealthKit.queryCorrelations({
  type: HealthKit.CorrelationType.bloodPressure,
  limit: 20,
});

Clinical reads also need isClinicalDataEnabled: true on the config plugin.

✍️ Write / delete

await HealthKit.saveQuantitySample({
  type: HealthKit.QuantityType.stepCount,
  unit: HealthKit.Unit.count,
  value: 100,
  startDate: new Date(Date.now() - 60_000),
  endDate: new Date(),
});

await HealthKit.saveCategorySample({
  type: HealthKit.CategoryType.sleepAnalysis,
  value: HealthKit.SleepAnalysisValue.asleepCore,
  startDate,
  endDate,
});

await HealthKit.saveWorkout({
  activityType: HealthKit.WorkoutActivityType.running,
  startDate,
  endDate,
  energyBurned: 400,
  energyBurnedUnit: HealthKit.Unit.kilocalorie,
  distance: 5000,
  distanceUnit: HealthKit.Unit.meter,
});

await HealthKit.saveCorrelation({
  type: HealthKit.CorrelationType.bloodPressure,
  startDate: new Date(),
  objects: [
    {
      type: HealthKit.QuantityType.bloodPressureSystolic,
      unit: HealthKit.Unit.millimeterOfMercury,
      value: 120,
    },
    {
      type: HealthKit.QuantityType.bloodPressureDiastolic,
      unit: HealthKit.Unit.millimeterOfMercury,
      value: 80,
    },
  ],
});

await HealthKit.deleteObjects({
  type: HealthKit.QuantityType.stepCount,
  uuid,
});

👤 Characteristics

getBiologicalSex(), getBloodType(), getDateOfBirth(), getFitzpatrickSkinType(), getWheelchairUse(). These are read-only HealthKit characteristics; include the matching CharacteristicType in toRead.

🔔 Background updates

await HealthKit.observe([HealthKit.QuantityType.stepCount]);

const subscription = HealthKit.addUpdateListener(async ({ type }) => {
  // Re-query that type. Return a promise: iOS keeps the app alive and holds
  // the HealthKit completion handler until it settles (~25s cap).
  await syncSteps();
});

await HealthKit.enableBackgroundDelivery(
  HealthKit.QuantityType.stepCount,
  HealthKit.UpdateFrequency.hourly
);

useHealthKitUpdates() is a React hook around the same event.

Background delivery needs isBackgroundDeliveryEnabled: true on the config plugin and the HealthKit background mode.

Killed-app relaunch. Types passed to observe are persisted natively. When iOS relaunches the app in the background for a HealthKit delivery, the observer queries are re-registered in didFinishLaunching (via an Expo AppDelegate subscriber), before the JS runtime boots. The delivery is queued until JS calls addUpdateListener, then dispatched. Register your listener at module scope (or early in the root component) so it exists on cold start; getObservedTypes() tells you what is already being observed. clearObservers() stops and forgets them.

Apple limits. Step count, distance, energy, exercise/stand time and similar activity types are delivered at most once per hour regardless of UpdateFrequency; heart rate, sleep, workouts and body measurements are not capped. Each delivery gets ~30 s of background time; the library completes at 25 s if your listener hasn't. The Simulator never relaunches a killed app for HealthKit — test on a device.

Debugging. Native logs are under the expo-healthkit os_log subsystem (restore, delivery, complete reason=js|timeout). Full guide with a device test recipe: .cursor/skills/expo-healthkit/background-delivery.md.

🏷️ Identifiers

Pass Apple's raw identifier strings. Named constants are provided for autocomplete; unknown identifiers still work if HealthKit knows them.

HealthKit.QuantityType.stepCount
// 'HKQuantityTypeIdentifierStepCount'

HealthKit.queryQuantitySamples({
  type: 'HKQuantityTypeIdentifierStepCount',
  unit: 'count',
});

App code should keep using Apple identifier strings on both platforms. See Cross-platform for what Health Connect maps, what stays iOS-only, and how to handle those APIs without Platform.OS forks.

🌍 Cross-platform

JavaScript always speaks HealthKit. You never pass Health Connect record class names from app code. The native layer chooses the backend:

flowchart LR
  App["App JS<br/>QuantityType.stepCount"] --> API["@appeeky/expo-healthkit"]
  API --> iOS["iOS<br/>HealthKit"]
  API --> Android["Android<br/>Health Connect"]

Mapped types (steps, heart rate, sleep, workouts, weight, blood pressure, nutrition, …) use the same method and identifier on both platforms. Unmapped identifiers are skipped in Android requestAuthorization, so an unsupported type and an empty one look alike; ask first:

const missing = HealthKit.getUnsupportedTypes([
  HealthKit.QuantityType.vo2Max,
  HealthKit.QuantityType.appleExerciseTime,
]);
// Android: ['HKQuantityTypeIdentifierAppleExerciseTime'] — say "not on this phone", not "no data"

Calling an Apple-only method on Android throws ERR_HEALTH_CONNECT_UNSUPPORTED instead of requiring if (Platform.OS === 'ios') on every query.

Hide Apple-only UI by catching that code:

try {
  const rings = await HealthKit.queryActivitySummaries({ from: today });
} catch (error) {
  if (error instanceof Error && 'code' in error && error.code === 'ERR_HEALTH_CONNECT_UNSUPPORTED') {
    return;
  }
  throw error;
}

What works on both

AreaIdentifiers / methods
ActivityQuantityType.stepCount, walking/running/cycling/swimming/wheelchair distance, flightsClimbed, pushCount, activeEnergyBurned, basalEnergyBurned
HeartheartRate, restingHeartRate, heartRateVariabilitySDNN, vo2Max, oxygenSaturation, respiratoryRate
BodybodyMass, height, bodyFatPercentage, leanBodyMass, bodyTemperature, bloodGlucose
Blood pressureCorrelationType.bloodPressure plus systolic / diastolic quantities
NutritionCorrelationType.food, dietaryWater, other HKQuantityTypeIdentifierDietary* types. A food correlation's HKFoodType metadata maps to NutritionRecord.name
SleepCategoryType.sleepAnalysis
WorkoutsqueryWorkouts / saveWorkout (WorkoutActivityType mapped onto Health Connect exercise types)
AggregatesqueryStatistics, queryStatisticsCollection, queryAnchored
WritessaveQuantitySample, saveCategorySample, saveCorrelation, deleteObjects

iOS-only

These throw ERR_HEALTH_CONNECT_UNSUPPORTED on Android. Health Connect has no equivalent (or no stable mapping yet):

APIWhy
queryElectrocardiogramsApple Watch ECG
queryActivitySummariesMove / Exercise / Stand rings
queryClinicalRecordsFHIR health records
queryAudiogramsHearing-test charts
queryWorkoutRouteWorkout GPS polylines
queryHeartbeatSeriesBeat-to-beat series
getBiologicalSex, getBloodType, getDateOfBirth, getFitzpatrickSkinType, getWheelchairUseHealthKit characteristics
observe, getObservedTypes, enableBackgroundDeliveryHealthKit observer queries / background delivery

Behavioral differences

iOSAndroid
BackendHealthKitHealth Connect (connect-client 1.1)
isAvailable()HealthKit presentHealth Connect installed (built-in on Android 14+; otherwise the Health Connect app)
Read permissionApple does not disclose read grants; getGrantedPermissions lists write grants onlygetAuthorizationStatus reflects granted Health Connect read or write; getGrantedPermissions returns the raw permission strings
RevokeNone (revokeAllPermissions throws ERR_HEALTHKIT_UNSUPPORTED)revokeAllPermissions()
Background readsObserver queries / background deliveryrequestAuthorization({ includeBackgroundRead: true }) plus the plugin's isHealthConnectBackgroundReadEnabled
Unmapped types in requestAuthorizationForwarded if HealthKit knows themSkipped
History windowGranted samples, any ageOften last 30 days unless the user also grants history access (the SDK requests it)
Anchored syncHKQueryAnchor (opaque string)Paged first sync, then Health Connect changes (opaque string). Expired tokens throw ERR_HEALTH_CONNECT_ANCHOR_EXPIRED — see Anchored sync. Do not reuse an iOS anchor on Android
HRVSDNN. Samples carry statistic: 'sdnn'Mapped to Health Connect RMSSD on the same heartRateVariabilitySDNN identifier — not the same statistic. Samples carry statistic: 'rmssd'; branch on it before charting the two together
Workout GPSqueryWorkoutRouteUnsupported
minSdkiOS 16.4API 26 (plugin raises it)

Do not fork mapped queries per platform. Fork only when you need different UI for an Apple-only feature.

🔌 Config plugin

PropDefaultPurpose
healthSharePermission"Allow $(PRODUCT_NAME) to read your health data"NSHealthShareUsageDescription
healthUpdatePermission"Allow $(PRODUCT_NAME) to write health data"NSHealthUpdateUsageDescription
healthClinicalRecordsPermissionclinical defaultUsed when isClinicalDataEnabled is true
isBackgroundDeliveryEnabledfalseHealthKit background delivery entitlement
isClinicalDataEnabledfalsehealth-records access
healthConnectPrivacyPolicyUrl—Shown on the Health Connect permission rationale screen
healthConnectPermissionsevery permission the module maps (47, incl. READ_HEALTH_DATA_HISTORY)Health Connect uses-permission entries to declare. READ_STEPS or android.permission.health.READ_STEPS. When set, only these are declared
isHealthConnectBackgroundReadEnabledfalseDeclares READ_HEALTH_DATA_IN_BACKGROUND for requestAuthorization({ includeBackgroundRead: true })

The plugin also sets android.minSdkVersion to 26 (Health Connect’s floor) unless the project already uses a higher value.

The plugin is the only place Health Connect permissions are declared; the module's own AndroidManifest.xml declares none. Play Console asks you to justify every declared Health Connect data type and the permission sheet shows the user the full list, so narrow it to what you query:

["@appeeky/expo-healthkit", { "healthConnectPermissions": ["READ_STEPS", "READ_HEART_RATE", "READ_SLEEP", "READ_HEALTH_DATA_HISTORY"] }]

A type you query without declaring its permission is skipped at requestAuthorization and reads as empty. HEALTH_CONNECT_PERMISSIONS (exported from the plugin) is the default list.

Set a permission string to false to skip writing that Info.plist key (if you manage it yourself).

📱 Example app

cd example
npm install
npx expo run:ios
# or
npx expo run:android

The example is an Expo SDK 57 app that autolinks this module on iOS and Android. It will not work in Expo Go.

🤖 Agent skills

Coding agents (Cursor, Claude Code, Copilot, and similar) should load the bundled skill.

Copy .cursor/skills/expo-healthkit/ into your app’s .cursor/skills/expo-healthkit/ (or the agent’s skills folder):

FileWhat agents get
SKILL.mdWhen to use which query, auth rules, and plugin flags
reference.mdMethod signatures, units, identifiers
examples.mdCopy-paste flows (steps, sleep, rings, blood pressure, food)

The skill tells the agent to pass Apple identifier strings through one query/save API on iOS and Android, request every type in toRead, and rebuild native code after Swift, Kotlin, or plugin changes.

🚀 Releases

Versions follow semver. Pushing to main with a Conventional Commit runs .github/workflows/release.yml: bump package.json, update CHANGELOG.md, GitHub Release, and npm publish.

Commitnpm bump
fix: …patch (0.1.0 → 0.1.1)
feat: …minor (0.1.0 → 0.2.0)
feat!: … or BREAKING CHANGE: in the bodymajor (0.1.0 → 1.0.0)
docs:, chore:, ci:, test:no release

Squash-merge PRs so the merge commit (or PR title) starts with fix: / feat:. chore: and ci: do not publish.

npm publishes from GitHub Actions via trusted publishing (no NPM_TOKEN). One-time setup: package Settings → Trusted Publisher → GitHub Actions, repository appeeky/expo-healthkit, workflow filename release.yml, and allow npm publish (not only staged publish).

🤝 Contributing

Issues and pull requests are welcome. Contributions are licensed under PolyForm Shield 1.0.0.

Checks

CI on main and every PR runs lint, typecheck, Jest, and an npm pack contents check (.github/workflows/ci.yml). Run the same locally before opening a PR:

npm ci
npm run lint
npm run typecheck
npm test -- --watchAll=false
npm run test:coverage

npm test opens Jest watch mode on a TTY. Pass --watchAll=false (as CI does) for a single run. test:coverage writes an HTML report to coverage/ and must stay green for the JavaScript that tests already cover (src/identifiers.ts, src/errors.ts, src/dates.ts).

What to test

ChangeExpected coverage
Identifiers, errors, date helpers, public JS typesAdd or extend Jest in src/__tests__/. Keep npm run test:coverage passing
Config pluginTypecheck plugin/; if you add pure helpers, put Jest next to them
Kotlin Health Connect mappingUpdate the Cross-platform tables and .cursor/skills/expo-healthkit/. Run the example on an Android device with Health Connect
Swift HealthKitSame skill/README update. Run the example on Simulator or a device with the HealthKit capability
Example UIcd example && npx expo run:ios / npx expo run:android. Health APIs do not run in Expo Go

Native HealthKit / Health Connect cannot run in Node. Device or Simulator verification is the coverage for Swift and Kotlin. Do not add Platform.OS branches in example/ for types that already map.

Native rebuild

Metro reload is not enough after Swift, Kotlin, or plugin edits:

npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:android

Docs

If the public API or Android mapping changes, update README.md and .cursor/skills/expo-healthkit/ (SKILL.md, reference.md, examples.md) in the same PR. CHANGELOG.md is generated on release from conventional commits.

🔒 Privacy

Health data stays on-device unless your app uploads it. Request only the types you need, explain why in the iOS usage strings, declare the matching Health Connect data types in Play Console, and follow Apple HealthKit and Google Health Connect review guidelines. Android reads are often limited to the last 30 days unless the user also grants history access.

📄 License

PolyForm Shield 1.0.0. See LICENSE for the full terms.

You can install and use this SDK inside your own Expo / React Native apps, including commercial products.

You cannot copy this SDK and sell, publish, or otherwise offer a competing HealthKit / Health Connect library based on it.

版本与发布记录

@appeeky/expo-healthkit 共发布 8 个版本,首次发布于 2026/09/06;过去 12 个月发布了 8 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.2latest2026/10/2711 天前
0.4.12026/10/021 个月前
0.4.02026/10/021 个月前
0.3.02026/10/021 个月前
0.2.12026/10/021 个月前
0.2.02026/09/091 个月前

关于 @appeeky/expo-healthkit 的常见问题

@appeeky/expo-healthkit 是什么?

Apple HealthKit and Android Health Connect for Expo. One identifier-based JavaScript API on iOS and Android.

如何安装 @appeeky/expo-healthkit?

在项目目录运行 npm install @appeeky/expo-healthkit,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:expo(>=53.0.0)、react(*)、react-native(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@appeeky/expo-healthkit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@appeeky/expo-healthkit 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@appeeky/expo-healthkit 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@appeeky/expo-healthkit 可以商用吗?

@appeeky/expo-healthkit 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@appeeky/expo-healthkit 的下载量有多少?

根据 npm 官方下载统计,@appeeky/expo-healthkit 在 2026/10/07 – 2026/11/05 这 30 天内被下载 5,097 次,日均约 170 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@appeeky/expo-healthkit 还在维护吗?

最新版本 0.4.2 发布于 2026/10/27(11 天前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/09/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。