@interval-health/capacitor-health 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,324 +1,912 @@
1
- # @capgo/capacitor-health
2
- <a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
1
+ # @interval-health/capacitor-health
3
2
 
4
- <div align="center">
5
- <h2><a href="https://capgo.app/?ref=plugin_health"> ➡️ Get Instant updates for your App with Capgo</a></h2>
6
- <h2><a href="https://capgo.app/consulting/?ref=plugin_health"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
7
- </div>
3
+ A Capacitor plugin to interact with health data from **Apple HealthKit** (iOS) and **Health Connect** (Android). This plugin provides a unified JavaScript API to read and write health and fitness data across both platforms.
8
4
 
9
- Capacitor plugin to read and write health metrics via Apple HealthKit (iOS) and Health Connect (Android). The TypeScript API keeps the same data types and units across platforms so you can build once and deploy everywhere.
5
+ ---
10
6
 
11
- ## Why Capacitor Health?
7
+ ## 📦 Installation
12
8
 
13
- The only **free**, **unified** health data plugin for Capacitor supporting the latest native APIs:
9
+ ```bash
10
+ npm install @interval-health/capacitor-health
11
+ ```
12
+
13
+ After installation, sync your native projects:
14
+
15
+ ```bash
16
+ npx cap sync
17
+ ```
14
18
 
15
- - **Health Connect (Android)** - Uses Google's newest health platform (replaces deprecated Google Fit)
16
- - **HealthKit (iOS)** - Full integration with Apple's health framework
17
- - **Unified API** - Same TypeScript interface across platforms with consistent units
18
- - **Multiple metrics** - Steps, distance, calories, heart rate, weight
19
- - **Read & Write** - Query historical data and save new health entries
20
- - **Modern standards** - Supports Android 8.0+ and iOS 14+
21
- - **Modern package management** - Supports both Swift Package Manager (SPM) and CocoaPods (SPM-ready for Capacitor 8)
19
+ ---
22
20
 
23
- Perfect for fitness apps, health trackers, wellness platforms, and medical applications.
21
+ ## 🚀 Supported Platforms
24
22
 
25
- ## Documentation
23
+ | Platform | Implementation | Minimum Version |
24
+ |----------|---------------|-----------------|
25
+ | **iOS** | Apple HealthKit | iOS 13.0+ |
26
+ | **Android** | Health Connect | Android 9.0+ (API 28) |
27
+ | **Web** | Not supported | - |
26
28
 
27
- The most complete doc is available here: https://capgo.app/docs/plugins/health/
29
+ ### Platform Requirements
28
30
 
29
- ## Install
31
+ #### iOS
32
+ - Xcode 14.0 or later
33
+ - iOS deployment target: 13.0+
34
+ - You must add the required usage descriptions to your `Info.plist`
30
35
 
31
- ```bash
32
- npm install @capgo/capacitor-health
33
- npx cap sync
34
- ```
36
+ #### Android
37
+ - Android Studio Arctic Fox or later
38
+ - Minimum SDK: 28 (Android 9.0)
39
+ - Target SDK: 34+
40
+ - Health Connect app must be installed on the device
35
41
 
36
- ## iOS Setup
42
+ ---
37
43
 
38
- 1. Open your Capacitor application's Xcode workspace and enable the **HealthKit** capability.
39
- 2. Provide usage descriptions in `Info.plist` (update the copy for your product):
44
+ ## 🔐 Permissions & Setup
45
+
46
+ ### iOS Setup
47
+
48
+ Add the following keys to your `ios/App/App/Info.plist` file:
40
49
 
41
50
  ```xml
42
51
  <key>NSHealthShareUsageDescription</key>
43
- <string>This app reads your health data to personalise your experience.</string>
52
+ <string>This app needs access to read your health data.</string>
44
53
  <key>NSHealthUpdateUsageDescription</key>
45
- <string>This app writes new health entries that you explicitly create.</string>
54
+ <string>This app needs access to write health data.</string>
55
+ ```
56
+
57
+ Enable the **HealthKit** capability in your Xcode project:
58
+ 1. Open your project in Xcode
59
+ 2. Select your app target
60
+ 3. Go to **Signing & Capabilities**
61
+ 4. Click **+ Capability** and add **HealthKit**
62
+
63
+ ### Android Setup
64
+
65
+ Add the Health Connect permissions to your `android/app/src/main/AndroidManifest.xml`:
66
+
67
+ ```xml
68
+ <manifest>
69
+ <!-- Health Connect permissions -->
70
+ <uses-permission android:name="android.permission.health.READ_STEPS"/>
71
+ <uses-permission android:name="android.permission.health.WRITE_STEPS"/>
72
+ <uses-permission android:name="android.permission.health.READ_DISTANCE"/>
73
+ <uses-permission android:name="android.permission.health.WRITE_DISTANCE"/>
74
+ <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
75
+ <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED"/>
76
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
77
+ <uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>
78
+ <uses-permission android:name="android.permission.health.READ_WEIGHT"/>
79
+ <uses-permission android:name="android.permission.health.WRITE_WEIGHT"/>
80
+ <uses-permission android:name="android.permission.health.READ_SLEEP"/>
81
+ <uses-permission android:name="android.permission.health.WRITE_SLEEP"/>
82
+
83
+ <application>
84
+ <!-- Required for Health Connect integration -->
85
+ <activity-alias
86
+ android:name="ViewPermissionUsageActivity"
87
+ android:exported="true"
88
+ android:targetActivity=".MainActivity"
89
+ android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
90
+ <intent-filter>
91
+ <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
92
+ </intent-filter>
93
+ </activity-alias>
94
+ </application>
95
+ </manifest>
46
96
  ```
47
97
 
48
- ## Android Setup
98
+ **Note**: Users must have the **Health Connect** app installed on their Android device. If not installed, the plugin will report that health data is unavailable.
49
99
 
50
- This plugin now uses [Health Connect](https://developer.android.com/health-and-fitness/guides/health-connect) instead of Google Fit. Make sure your app meets the requirements below:
100
+ ---
51
101
 
52
- 1. **Min SDK 26+.** Health Connect is only available on Android 8.0 (API 26) and above. The plugin’s Gradle setup already targets this level.
53
- 2. **Declare Health permissions.** The plugin manifest ships with the required `<uses-permission>` declarations (`READ_/WRITE_STEPS`, `READ_/WRITE_DISTANCE`, `READ_/WRITE_ACTIVE_CALORIES_BURNED`, `READ_/WRITE_HEART_RATE`, `READ_/WRITE_WEIGHT`). Your app does not need to duplicate them, but you must surface a user-facing rationale because the permissions are considered health sensitive.
54
- 3. **Ensure Health Connect is installed.** Devices on Android 14+ include it by default. For earlier versions the user must install *Health Connect by Android* from the Play Store. The `Health.isAvailable()` helper exposes the current status so you can prompt accordingly.
55
- 4. **Request runtime access.** The plugin opens the Health Connect permission UI when you call `requestAuthorization`. You should still handle denial flows (e.g., show a message if `checkAuthorization` reports missing scopes).
102
+ ## 📖 Usage Guide
56
103
 
57
- If you already used Google Fit in your project you can remove the associated dependencies (`play-services-fitness`, `play-services-auth`, OAuth configuration, etc.).
104
+ ### Import the Plugin
105
+
106
+ ```typescript
107
+ import { Health } from '@interval-health/capacitor-health';
108
+ ```
58
109
 
59
- ## Usage
110
+ ### Complete Workflow Example
60
111
 
61
- ```ts
62
- import { Health } from '@capgo/capacitor-health';
112
+ Here's a typical flow for working with health data:
63
113
 
64
- // Verify that the native health SDK is present on this device
65
- const availability = await Health.isAvailable();
66
- if (!availability.available) {
67
- console.warn('Health access unavailable:', availability.reason);
114
+ ```typescript
115
+ import { Health } from '@interval-health/capacitor-health';
116
+
117
+ async function setupHealthData() {
118
+ // 1. Check if health services are available
119
+ const availability = await Health.isAvailable();
120
+
121
+ if (!availability.available) {
122
+ console.error('Health data unavailable:', availability.reason);
123
+ return;
124
+ }
125
+
126
+ // 2. Request authorization for data types
127
+ const authStatus = await Health.requestAuthorization({
128
+ read: ['steps', 'heartRate', 'weight', 'sleep'],
129
+ write: ['steps', 'weight']
130
+ });
131
+
132
+ console.log('Authorized to read:', authStatus.readAuthorized);
133
+ console.log('Denied to read:', authStatus.readDenied);
134
+
135
+ // 3. Read health samples
136
+ const stepsData = await Health.readSamples({
137
+ dataType: 'steps',
138
+ startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days ago
139
+ endDate: new Date().toISOString(),
140
+ limit: 100,
141
+ ascending: false
142
+ });
143
+
144
+ console.log('Steps data:', stepsData.samples);
145
+
146
+ // 4. Write a health sample
147
+ await Health.saveSample({
148
+ dataType: 'weight',
149
+ value: 70.5,
150
+ startDate: new Date().toISOString()
151
+ });
152
+
153
+ console.log('Weight saved successfully!');
68
154
  }
155
+ ```
69
156
 
70
- // Ask for separate read/write access scopes
71
- await Health.requestAuthorization({
72
- read: ['steps', 'heartRate', 'weight', 'sleep'],
73
- write: ['weight', 'sleep'],
74
- });
157
+ ---
75
158
 
76
- // Query the last 50 step samples from the past 24 hours
77
- const { samples } = await Health.readSamples({
78
- dataType: 'steps',
79
- startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
80
- endDate: new Date().toISOString(),
81
- limit: 50,
82
- });
159
+ ## 📚 API Reference
83
160
 
84
- // Persist a new body-weight entry (kilograms by default)
85
- await Health.saveSample({
86
- dataType: 'weight',
87
- value: 74.3,
88
- });
161
+ ### Health Data Types
89
162
 
90
- // Query sleep data from the past week
91
- const { samples: sleepSamples } = await Health.readSamples({
92
- dataType: 'sleep',
93
- startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
94
- endDate: new Date().toISOString(),
163
+ The plugin supports the following health data types:
164
+
165
+ ```typescript
166
+ export type HealthDataType =
167
+ | 'steps'
168
+ | 'distance'
169
+ | 'calories'
170
+ | 'heartRate'
171
+ | 'weight'
172
+ | 'sleep'
173
+ | 'mobility'
174
+ | 'activity'
175
+ | 'heart'
176
+ | 'body'
177
+ | 'workout';
178
+ ```
179
+
180
+ | Data Type | Description | Unit | iOS | Android | Read | Write |
181
+ |-----------|-------------|------|-----|---------|------|-------|
182
+ | `steps` | Step count | count | ✅ | ✅ | ✅ | ✅ |
183
+ | `distance` | Walking/running distance | meter | ✅ | ✅ | ✅ | ✅ |
184
+ | `calories` | Active calories burned | kilocalorie | ✅ | ✅ | ✅ | ✅ |
185
+ | `heartRate` | Heart rate | bpm (beats per minute) | ✅ | ✅ | ✅ | ✅ |
186
+ | `weight` | Body weight | kilogram | ✅ | ✅ | ✅ | ✅ |
187
+ | `sleep` | Sleep duration and stages | minute | ✅ | ❌ | ✅ | ✅ |
188
+ | `mobility` | Mobility metrics | mixed | ✅ | ❌ | ✅ | ❌ |
189
+ | `activity` | Activity metrics | mixed | ✅ | ❌ | ✅ | ❌ |
190
+ | `heart` | Heart health metrics | mixed | ✅ | ❌ | ✅ | ❌ |
191
+ | `body` | Body measurements | mixed | ✅ | ❌ | ✅ | ❌ |
192
+ | `workout` | Workout/exercise sessions | minute | ✅ | ❌ | ✅ | ❌ |
193
+
194
+ **Platform Support Notes**:
195
+ - **iOS** supports all 11 data types
196
+ - **Android** supports only 5 basic data types: `steps`, `distance`, `calories`, `heartRate`, and `weight`
197
+ - The `sleep`, `mobility`, `activity`, `heart`, `body`, and `workout` types are **iOS-only** and not available on Android
198
+ - Composite types (`mobility`, `activity`, `heart`, `body`, `workout`) are **read-only** on iOS
199
+
200
+ ### Sleep States
201
+
202
+ When reading sleep data, each sample may include a `sleepState` property:
203
+
204
+ | State | Description |
205
+ |-------|-------------|
206
+ | `inBed` | User is in bed but not necessarily asleep |
207
+ | `asleep` | General sleep state (when specific stage unknown) |
208
+ | `awake` | User is awake during sleep period |
209
+ | `asleepCore` | Core/light sleep stage |
210
+ | `asleepDeep` | Deep sleep stage |
211
+ | `asleepREM` | REM (Rapid Eye Movement) sleep stage |
212
+ | `unknown` | Sleep state could not be determined |
213
+
214
+ ### Workout Data
215
+
216
+ When reading workout data (`dataType: 'workout'`), the returned data structure includes:
217
+
218
+ ```typescript
219
+ interface WorkoutData {
220
+ date: string; // ISO date (YYYY-MM-DD)
221
+ type: string; // Activity type (e.g., "Running", "Cycling", "Swimming")
222
+ duration: number; // Duration in minutes
223
+ distance?: number; // Distance in miles (optional)
224
+ calories?: number; // Calories burned (optional)
225
+ source?: string; // Source app name (optional)
226
+ avgHeartRate?: number; // Average heart rate in BPM (optional)
227
+ maxHeartRate?: number; // Maximum heart rate in BPM (optional)
228
+ zones?: { // Heart rate zones in minutes (optional)
229
+ zone1?: number; // 50-60% max HR
230
+ zone2?: number; // 60-70% max HR
231
+ zone3?: number; // 70-80% max HR
232
+ zone4?: number; // 80-90% max HR
233
+ zone5?: number; // 90-100% max HR
234
+ };
235
+ }
236
+ ```
237
+
238
+ **Supported Workout Types**: Running, Cycling, Walking, Swimming, Yoga, FunctionalStrengthTraining, TraditionalStrengthTraining, Elliptical, Rowing, Hiking, HighIntensityIntervalTraining, Dance, Basketball, Soccer, Tennis, Golf, StairClimbing, and more.
239
+
240
+ **Example**:
241
+ ```typescript
242
+ const result = await Health.readSamples({
243
+ dataType: 'workout',
244
+ startDate: '2024-01-01T00:00:00Z',
245
+ endDate: '2024-01-31T23:59:59Z',
246
+ limit: 50
95
247
  });
96
- // Each sleep sample includes: value (duration in minutes), sleepState (stage), startDate, endDate
248
+
249
+ // Sample output:
250
+ // {
251
+ // date: "2024-01-15",
252
+ // type: "Running",
253
+ // duration: 45,
254
+ // distance: 5.23,
255
+ // calories: 450,
256
+ // source: "Apple Watch",
257
+ // avgHeartRate: 145,
258
+ // maxHeartRate: 175,
259
+ // zones: { zone2: 10, zone3: 20, zone4: 15 }
260
+ // }
97
261
  ```
98
262
 
99
- ### Supported data types
263
+ ---
264
+
265
+ ## 🔧 Methods
266
+
267
+ ### isAvailable()
268
+
269
+ Check if health services are available on the current platform.
100
270
 
101
- | Identifier | Default unit | Notes |
102
- | ---------- | ------------- | ----- |
103
- | `steps` | `count` | Step count deltas |
104
- | `distance` | `meter` | Walking / running distance |
105
- | `calories` | `kilocalorie` | Active energy burned |
106
- | `heartRate`| `bpm` | Beats per minute |
107
- | `weight` | `kilogram` | Body mass |
108
- | `sleep` | `minute` | Sleep sessions with stages (inBed, asleep, awake, asleepCore, asleepDeep, asleepREM) |
271
+ ```typescript
272
+ Health.isAvailable(): Promise<AvailabilityResult>
273
+ ```
109
274
 
110
- All write operations expect the default unit shown above. On Android the `metadata` option is currently ignored by Health Connect.
275
+ **Returns**: `Promise<AvailabilityResult>`
111
276
 
112
- ## API
277
+ **Example**:
278
+ ```typescript
279
+ const result = await Health.isAvailable();
113
280
 
114
- <docgen-index>
281
+ if (result.available) {
282
+ console.log('Health services available on:', result.platform);
283
+ } else {
284
+ console.log('Unavailable reason:', result.reason);
285
+ }
286
+ ```
115
287
 
116
- * [`isAvailable()`](#isavailable)
117
- * [`requestAuthorization(...)`](#requestauthorization)
118
- * [`checkAuthorization(...)`](#checkauthorization)
119
- * [`readSamples(...)`](#readsamples)
120
- * [`saveSample(...)`](#savesample)
121
- * [`getPluginVersion()`](#getpluginversion)
122
- * [Interfaces](#interfaces)
123
- * [Type Aliases](#type-aliases)
288
+ **Response Example**:
289
+ ```typescript
290
+ {
291
+ available: true,
292
+ platform: 'ios' // or 'android'
293
+ }
124
294
 
125
- </docgen-index>
295
+ // When unavailable:
296
+ {
297
+ available: false,
298
+ platform: 'android',
299
+ reason: 'Health Connect is unavailable on this device.'
300
+ }
301
+ ```
126
302
 
127
- <docgen-api>
128
- <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
303
+ ---
129
304
 
130
- ### isAvailable()
305
+ ### requestAuthorization()
131
306
 
307
+ Request permission to read and/or write specific health data types. This will show the platform's native permission dialog.
308
+
309
+ ```typescript
310
+ Health.requestAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>
311
+ ```
312
+
313
+ **Parameters**:
314
+ - `options.read` (optional): Array of data types you want to read
315
+ - `options.write` (optional): Array of data types you want to write
316
+
317
+ **Returns**: `Promise<AuthorizationStatus>`
318
+
319
+ **Example**:
132
320
  ```typescript
133
- isAvailable() => Promise<AvailabilityResult>
321
+ const status = await Health.requestAuthorization({
322
+ read: ['steps', 'heartRate', 'sleep'],
323
+ write: ['steps', 'weight']
324
+ });
325
+
326
+ console.log('Read authorized:', status.readAuthorized);
327
+ console.log('Read denied:', status.readDenied);
328
+ console.log('Write authorized:', status.writeAuthorized);
329
+ console.log('Write denied:', status.writeDenied);
134
330
  ```
135
331
 
136
- Returns whether the current platform supports the native health SDK.
332
+ **Response Example**:
333
+ ```typescript
334
+ {
335
+ readAuthorized: ['steps', 'heartRate'],
336
+ readDenied: ['sleep'],
337
+ writeAuthorized: ['steps', 'weight'],
338
+ writeDenied: []
339
+ }
340
+ ```
137
341
 
138
- **Returns:** <code>Promise&lt;<a href="#availabilityresult">AvailabilityResult</a>&gt;</code>
342
+ **Important Notes**:
343
+ - On **iOS**, the HealthKit API doesn't reveal whether the user granted or denied read permission (for privacy reasons). The `readAuthorized` array will contain all requested types, even if some were denied.
344
+ - On **Android**, Health Connect provides accurate permission status.
345
+ - You must call this method before reading or writing health data.
139
346
 
140
- --------------------
347
+ ---
141
348
 
349
+ ### checkAuthorization()
142
350
 
143
- ### requestAuthorization(...)
351
+ Check the current authorization status without prompting the user.
144
352
 
145
353
  ```typescript
146
- requestAuthorization(options: AuthorizationOptions) => Promise<AuthorizationStatus>
354
+ Health.checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>
147
355
  ```
148
356
 
149
- Requests read/write access to the provided data types.
357
+ **Parameters**: Same as `requestAuthorization()`
358
+
359
+ **Returns**: `Promise<AuthorizationStatus>`
360
+
361
+ **Example**:
362
+ ```typescript
363
+ const status = await Health.checkAuthorization({
364
+ read: ['steps', 'heartRate'],
365
+ write: ['weight']
366
+ });
150
367
 
151
- | Param | Type |
152
- | ------------- | --------------------------------------------------------------------- |
153
- | **`options`** | <code><a href="#authorizationoptions">AuthorizationOptions</a></code> |
368
+ if (status.readAuthorized.includes('steps')) {
369
+ // We have permission to read steps
370
+ await readStepsData();
371
+ }
372
+ ```
154
373
 
155
- **Returns:** <code>Promise&lt;<a href="#authorizationstatus">AuthorizationStatus</a>&gt;</code>
374
+ **Response Example**: Same structure as `requestAuthorization()`
156
375
 
157
- --------------------
376
+ ---
158
377
 
378
+ ### readSamples()
159
379
 
160
- ### checkAuthorization(...)
380
+ Read health samples for a specific data type within a time range.
161
381
 
162
382
  ```typescript
163
- checkAuthorization(options: AuthorizationOptions) => Promise<AuthorizationStatus>
383
+ Health.readSamples(options: QueryOptions): Promise<ReadSamplesResult>
164
384
  ```
165
385
 
166
- Checks authorization status for the provided data types without prompting the user.
386
+ **Parameters**:
387
+ - `options.dataType` (required): The type of health data to retrieve
388
+ - `options.startDate` (optional): ISO 8601 start date (inclusive). Defaults to 24 hours ago
389
+ - `options.endDate` (optional): ISO 8601 end date (exclusive). Defaults to now
390
+ - `options.limit` (optional): Maximum number of samples to return. Defaults to 100
391
+ - `options.ascending` (optional): Sort results by start date ascending. Defaults to false (descending)
392
+
393
+ **Returns**: `Promise<ReadSamplesResult>`
167
394
 
168
- | Param | Type |
169
- | ------------- | --------------------------------------------------------------------- |
170
- | **`options`** | <code><a href="#authorizationoptions">AuthorizationOptions</a></code> |
395
+ **Example**:
396
+ ```typescript
397
+ // Read last 7 days of step data
398
+ const result = await Health.readSamples({
399
+ dataType: 'steps',
400
+ startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
401
+ endDate: new Date().toISOString(),
402
+ limit: 50,
403
+ ascending: false
404
+ });
171
405
 
172
- **Returns:** <code>Promise&lt;<a href="#authorizationstatus">AuthorizationStatus</a>&gt;</code>
406
+ result.samples.forEach(sample => {
407
+ console.log(`${sample.value} ${sample.unit} on ${sample.startDate}`);
408
+ });
409
+ ```
173
410
 
174
- --------------------
411
+ **Response Example**:
412
+ ```typescript
413
+ {
414
+ samples: [
415
+ {
416
+ dataType: 'steps',
417
+ value: 8543,
418
+ unit: 'count',
419
+ startDate: '2025-12-17T00:00:00.000Z',
420
+ endDate: '2025-12-17T23:59:59.999Z',
421
+ sourceName: 'Apple Watch',
422
+ sourceId: 'com.apple.health'
423
+ },
424
+ {
425
+ dataType: 'steps',
426
+ value: 12032,
427
+ unit: 'count',
428
+ startDate: '2025-12-16T00:00:00.000Z',
429
+ endDate: '2025-12-16T23:59:59.999Z',
430
+ sourceName: 'iPhone',
431
+ sourceId: 'com.apple.health'
432
+ }
433
+ ]
434
+ }
435
+ ```
175
436
 
437
+ **Sleep Data Example**:
438
+ ```typescript
439
+ const sleepResult = await Health.readSamples({
440
+ dataType: 'sleep',
441
+ startDate: '2025-12-16T00:00:00.000Z',
442
+ endDate: '2025-12-17T00:00:00.000Z'
443
+ });
176
444
 
177
- ### readSamples(...)
445
+ // Sleep samples include sleepState information
446
+ sleepResult.samples.forEach(sample => {
447
+ console.log(`Sleep: ${sample.value} ${sample.unit}, State: ${sample.sleepState}`);
448
+ });
449
+ ```
178
450
 
451
+ **Response Example for Sleep**:
179
452
  ```typescript
180
- readSamples(options: QueryOptions) => Promise<ReadSamplesResult>
453
+ {
454
+ samples: [
455
+ {
456
+ dataType: 'sleep',
457
+ value: 450,
458
+ unit: 'minute',
459
+ startDate: '2025-12-16T22:30:00.000Z',
460
+ endDate: '2025-12-17T06:00:00.000Z',
461
+ sleepState: 'asleepDeep',
462
+ sourceName: 'Sleep App'
463
+ }
464
+ ]
465
+ }
181
466
  ```
182
467
 
183
- Reads samples for the given data type within the specified time frame.
468
+ ---
184
469
 
185
- | Param | Type |
186
- | ------------- | ----------------------------------------------------- |
187
- | **`options`** | <code><a href="#queryoptions">QueryOptions</a></code> |
470
+ ### saveSample()
188
471
 
189
- **Returns:** <code>Promise&lt;<a href="#readsamplesresult">ReadSamplesResult</a>&gt;</code>
472
+ Write a single health sample to the native health store.
190
473
 
191
- --------------------
474
+ ```typescript
475
+ Health.saveSample(options: WriteSampleOptions): Promise<void>
476
+ ```
192
477
 
478
+ **Parameters**:
479
+ - `options.dataType` (required): The type of health data to save
480
+ - `options.value` (required): The numeric value
481
+ - `options.unit` (optional): Unit override (must match the data type's expected unit)
482
+ - `options.startDate` (optional): ISO 8601 start date. Defaults to now
483
+ - `options.endDate` (optional): ISO 8601 end date. Defaults to startDate
484
+ - `options.metadata` (optional): Additional key-value metadata (platform support varies)
193
485
 
194
- ### saveSample(...)
486
+ **Returns**: `Promise<void>`
195
487
 
488
+ **Example - Save Weight**:
196
489
  ```typescript
197
- saveSample(options: WriteSampleOptions) => Promise<void>
490
+ await Health.saveSample({
491
+ dataType: 'weight',
492
+ value: 72.5,
493
+ startDate: new Date().toISOString()
494
+ });
198
495
  ```
199
496
 
200
- Writes a single sample to the native health store.
497
+ **Example - Save Steps with Time Range**:
498
+ ```typescript
499
+ const workoutStart = new Date('2025-12-17T10:00:00.000Z');
500
+ const workoutEnd = new Date('2025-12-17T11:30:00.000Z');
501
+
502
+ await Health.saveSample({
503
+ dataType: 'steps',
504
+ value: 5000,
505
+ startDate: workoutStart.toISOString(),
506
+ endDate: workoutEnd.toISOString(),
507
+ metadata: {
508
+ 'workout': 'morning run',
509
+ 'location': 'park'
510
+ }
511
+ });
512
+ ```
201
513
 
202
- | Param | Type |
203
- | ------------- | ----------------------------------------------------------------- |
204
- | **`options`** | <code><a href="#writesampleoptions">WriteSampleOptions</a></code> |
514
+ **Example - Save Heart Rate**:
515
+ ```typescript
516
+ await Health.saveSample({
517
+ dataType: 'heartRate',
518
+ value: 75,
519
+ unit: 'bpm',
520
+ startDate: new Date().toISOString()
521
+ });
522
+ ```
205
523
 
206
- --------------------
524
+ **Unit Validation**: The plugin validates that the provided unit matches the expected unit for the data type. For example:
525
+ - `steps` expects `count`
526
+ - `distance` expects `meter`
527
+ - `calories` expects `kilocalorie`
528
+ - `heartRate` expects `bpm`
529
+ - `weight` expects `kilogram`
207
530
 
531
+ ---
208
532
 
209
533
  ### getPluginVersion()
210
534
 
535
+ Get the current version of the native plugin.
536
+
537
+ ```typescript
538
+ Health.getPluginVersion(): Promise<{ version: string }>
539
+ ```
540
+
541
+ **Returns**: `Promise<{ version: string }>`
542
+
543
+ **Example**:
544
+ ```typescript
545
+ const { version } = await Health.getPluginVersion();
546
+ console.log('Plugin version:', version);
547
+ ```
548
+
549
+ ---
550
+
551
+ ## 💡 Common Usage Patterns
552
+
553
+ ### 1. Displaying Daily Step Count
554
+
555
+ ```typescript
556
+ async function getDailySteps() {
557
+ const today = new Date();
558
+ today.setHours(0, 0, 0, 0);
559
+
560
+ const result = await Health.readSamples({
561
+ dataType: 'steps',
562
+ startDate: today.toISOString(),
563
+ endDate: new Date().toISOString()
564
+ });
565
+
566
+ const totalSteps = result.samples.reduce((sum, sample) => sum + sample.value, 0);
567
+ console.log('Steps today:', totalSteps);
568
+
569
+ return totalSteps;
570
+ }
571
+ ```
572
+
573
+ ### 2. Weekly Activity Summary
574
+
575
+ ```typescript
576
+ async function getWeeklySummary() {
577
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
578
+
579
+ const [steps, distance, calories] = await Promise.all([
580
+ Health.readSamples({
581
+ dataType: 'steps',
582
+ startDate: weekAgo.toISOString(),
583
+ limit: 500
584
+ }),
585
+ Health.readSamples({
586
+ dataType: 'distance',
587
+ startDate: weekAgo.toISOString(),
588
+ limit: 500
589
+ }),
590
+ Health.readSamples({
591
+ dataType: 'calories',
592
+ startDate: weekAgo.toISOString(),
593
+ limit: 500
594
+ })
595
+ ]);
596
+
597
+ return {
598
+ totalSteps: steps.samples.reduce((sum, s) => sum + s.value, 0),
599
+ totalDistance: distance.samples.reduce((sum, s) => sum + s.value, 0),
600
+ totalCalories: calories.samples.reduce((sum, s) => sum + s.value, 0)
601
+ };
602
+ }
603
+ ```
604
+
605
+ ### 3. Logging Weight Over Time
606
+
607
+ ```typescript
608
+ async function getWeightHistory(days: number = 30) {
609
+ const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
610
+
611
+ const result = await Health.readSamples({
612
+ dataType: 'weight',
613
+ startDate: startDate.toISOString(),
614
+ ascending: true
615
+ });
616
+
617
+ return result.samples.map(sample => ({
618
+ date: new Date(sample.startDate).toLocaleDateString(),
619
+ weight: sample.value,
620
+ unit: sample.unit
621
+ }));
622
+ }
623
+ ```
624
+
625
+ ### 4. Sleep Analysis
626
+
627
+ ```typescript
628
+ async function getLastNightSleep() {
629
+ const yesterday = new Date();
630
+ yesterday.setDate(yesterday.getDate() - 1);
631
+ yesterday.setHours(18, 0, 0, 0); // Start from 6 PM yesterday
632
+
633
+ const result = await Health.readSamples({
634
+ dataType: 'sleep',
635
+ startDate: yesterday.toISOString(),
636
+ endDate: new Date().toISOString()
637
+ });
638
+
639
+ // Calculate total sleep time and breakdown by stage
640
+ const sleepByStage: Record<string, number> = {};
641
+ let totalSleep = 0;
642
+
643
+ result.samples.forEach(sample => {
644
+ const state = sample.sleepState || 'unknown';
645
+ sleepByStage[state] = (sleepByStage[state] || 0) + sample.value;
646
+ totalSleep += sample.value;
647
+ });
648
+
649
+ return {
650
+ totalMinutes: totalSleep,
651
+ totalHours: (totalSleep / 60).toFixed(1),
652
+ breakdown: sleepByStage
653
+ };
654
+ }
655
+ ```
656
+
657
+ ### 5. Recording a Workout
658
+
659
+ ```typescript
660
+ async function recordWorkout() {
661
+ const workoutStart = new Date(Date.now() - 45 * 60 * 1000); // 45 minutes ago
662
+ const workoutEnd = new Date();
663
+
664
+ // Save multiple metrics from the workout
665
+ await Promise.all([
666
+ Health.saveSample({
667
+ dataType: 'steps',
668
+ value: 4500,
669
+ startDate: workoutStart.toISOString(),
670
+ endDate: workoutEnd.toISOString()
671
+ }),
672
+ Health.saveSample({
673
+ dataType: 'distance',
674
+ value: 3500, // 3.5 km in meters
675
+ startDate: workoutStart.toISOString(),
676
+ endDate: workoutEnd.toISOString()
677
+ }),
678
+ Health.saveSample({
679
+ dataType: 'calories',
680
+ value: 320,
681
+ startDate: workoutStart.toISOString(),
682
+ endDate: workoutEnd.toISOString()
683
+ })
684
+ ]);
685
+
686
+ console.log('Workout recorded successfully!');
687
+ }
688
+ ```
689
+
690
+ ---
691
+
692
+ ## ⚠️ Error Handling
693
+
694
+ ### Common Errors and Solutions
695
+
211
696
  ```typescript
212
- getPluginVersion() => Promise<{ version: string; }>
697
+ async function safeHealthRead() {
698
+ try {
699
+ // Check availability first
700
+ const availability = await Health.isAvailable();
701
+ if (!availability.available) {
702
+ throw new Error(`Health unavailable: ${availability.reason}`);
703
+ }
704
+
705
+ // Request authorization
706
+ const authStatus = await Health.requestAuthorization({
707
+ read: ['steps']
708
+ });
709
+
710
+ if (authStatus.readDenied.includes('steps')) {
711
+ throw new Error('User denied permission to read steps');
712
+ }
713
+
714
+ // Read data
715
+ const result = await Health.readSamples({
716
+ dataType: 'steps',
717
+ startDate: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
718
+ });
719
+
720
+ return result.samples;
721
+
722
+ } catch (error) {
723
+ console.error('Health data error:', error);
724
+
725
+ // Handle specific error cases
726
+ if (error.message.includes('unavailable')) {
727
+ alert('Please install Health Connect (Android) or enable HealthKit (iOS)');
728
+ } else if (error.message.includes('permission')) {
729
+ alert('Please grant health data permissions in settings');
730
+ } else if (error.message.includes('Unsupported data type')) {
731
+ alert('This health metric is not supported on your device');
732
+ } else {
733
+ alert('Unable to access health data. Please try again.');
734
+ }
735
+
736
+ return [];
737
+ }
738
+ }
213
739
  ```
214
740
 
215
- Get the native Capacitor plugin version
741
+ ### Error Types
742
+
743
+ | Error | Cause | Solution |
744
+ |-------|-------|----------|
745
+ | `Health data is not available` | HealthKit/Health Connect not available | Check device compatibility |
746
+ | `Unsupported data type` | Invalid dataType parameter | Use one of the supported data types |
747
+ | `dataType is required` | Missing required parameter | Provide dataType in options |
748
+ | `value is required` | Missing value for saveSample | Provide numeric value |
749
+ | `Invalid ISO 8601 date` | Malformed date string | Use proper ISO 8601 format: `new Date().toISOString()` |
750
+ | `endDate must be greater than startDate` | Invalid date range | Ensure endDate >= startDate |
751
+ | `Health Connect needs an update` | Outdated Health Connect app | Update Health Connect from Play Store |
752
+ | `Unsupported unit` | Wrong unit for data type | Use the correct unit or omit to use default |
216
753
 
217
- **Returns:** <code>Promise&lt;{ version: string; }&gt;</code>
754
+ ---
218
755
 
219
- --------------------
756
+ ## 🔒 Privacy & Security
220
757
 
758
+ ### iOS Privacy Considerations
221
759
 
222
- ### Interfaces
760
+ - **HealthKit data never leaves the device** unless explicitly shared by your app
761
+ - Apple's HealthKit restricts read authorization status for privacy—your app cannot definitively know if read permission was denied
762
+ - Always provide clear explanations in your usage description strings
763
+ - Consider implementing fallback flows if users deny permissions
223
764
 
765
+ ### Android Privacy Considerations
224
766
 
225
- #### AvailabilityResult
767
+ - Health Connect provides transparent permission management
768
+ - Users can revoke permissions at any time through system settings
769
+ - Your app should handle permission changes gracefully
770
+ - Health Connect shows users which apps access their data
226
771
 
227
- | Prop | Type | Description |
228
- | --------------- | ---------------------------------------- | ------------------------------------------------------ |
229
- | **`available`** | <code>boolean</code> | |
230
- | **`platform`** | <code>'ios' \| 'android' \| 'web'</code> | Platform specific details (for debugging/diagnostics). |
231
- | **`reason`** | <code>string</code> | |
772
+ ### Best Practices
232
773
 
774
+ 1. **Request only what you need**: Don't request access to all data types if you only need steps
775
+ 2. **Explain before asking**: Show UI explaining why you need health data before calling `requestAuthorization()`
776
+ 3. **Handle denials gracefully**: Provide alternative functionality if permissions are denied
777
+ 4. **Respect user privacy**: Don't store sensitive health data on external servers without explicit consent
778
+ 5. **Test permission flows**: Test your app's behavior when permissions are denied or revoked
233
779
 
234
- #### AuthorizationStatus
780
+ ---
235
781
 
236
- | Prop | Type |
237
- | --------------------- | ----------------------------- |
238
- | **`readAuthorized`** | <code>HealthDataType[]</code> |
239
- | **`readDenied`** | <code>HealthDataType[]</code> |
240
- | **`writeAuthorized`** | <code>HealthDataType[]</code> |
241
- | **`writeDenied`** | <code>HealthDataType[]</code> |
782
+ ## 🐛 Known Limitations & Issues
242
783
 
784
+ ### iOS Limitations
243
785
 
244
- #### AuthorizationOptions
786
+ 1. **Read Authorization Status**: HealthKit doesn't reveal whether users denied read permissions (privacy feature)
787
+ 2. **Background Access**: Reading health data in the background requires additional setup with Background Modes capability
788
+ 3. **Composite Types**: `mobility`, `activity`, `heart`, and `body` are iOS-only aggregate types that return data from multiple HealthKit sources
789
+ 4. **Write Authorization**: Apps can only write data types they created or have explicit write permission for
245
790
 
246
- | Prop | Type | Description |
247
- | ----------- | ----------------------------- | ------------------------------------------------------- |
248
- | **`read`** | <code>HealthDataType[]</code> | Data types that should be readable after authorization. |
249
- | **`write`** | <code>HealthDataType[]</code> | Data types that should be writable after authorization. |
791
+ ### Android Limitations
250
792
 
793
+ 1. **Health Connect Required**: Users must have the Health Connect app installed (available on Android 9+)
794
+ 2. **Device Support**: Not all Android devices support Health Connect (mainly newer devices)
795
+ 3. **Limited Data Types**: Android implementation supports fewer composite types than iOS
796
+ 4. **API Level**: Requires minimum API level 28 (Android 9.0)
251
797
 
252
- #### ReadSamplesResult
798
+ ### General Limitations
253
799
 
254
- | Prop | Type |
255
- | ------------- | --------------------------- |
256
- | **`samples`** | <code>HealthSample[]</code> |
800
+ 1. **No Web Support**: This plugin does not work on web platforms (browser)
801
+ 2. **Data Sync Delays**: Health data may take time to sync between devices/apps
802
+ 3. **Source Variability**: Different apps and devices may report the same metrics differently
803
+ 4. **Historical Data**: Very old data (>1 year) may not be available depending on device settings
804
+ 5. **Unit Conversions**: The plugin uses specific units for each data type—unit conversion must be done in your app code
257
805
 
806
+ ---
258
807
 
259
- #### HealthSample
808
+ ## 📱 Platform-Specific Notes
260
809
 
261
- | Prop | Type | Description |
262
- | ---------------- | --------------------------------------------------------- | --------------------------------------------------- |
263
- | **`dataType`** | <code><a href="#healthdatatype">HealthDataType</a></code> | |
264
- | **`value`** | <code>number</code> | |
265
- | **`unit`** | <code><a href="#healthunit">HealthUnit</a></code> | |
266
- | **`startDate`** | <code>string</code> | |
267
- | **`endDate`** | <code>string</code> | |
268
- | **`sourceName`** | <code>string</code> | |
269
- | **`sourceId`** | <code>string</code> | |
270
- | **`sleepState`** | <code><a href="#sleepstate">SleepState</a></code> | Sleep state (only present when dataType is 'sleep') |
810
+ ### iOS (HealthKit)
271
811
 
812
+ - Requires physical iOS device for testing (Simulator has limited support)
813
+ - Some health metrics require specific hardware (Apple Watch for certain heart rate measurements)
814
+ - Sleep data quality depends on the user's sleep tracking app (Apple Watch, third-party apps)
815
+ - HealthKit automatically aggregates data from multiple sources
272
816
 
273
- #### QueryOptions
817
+ ### Android (Health Connect)
274
818
 
275
- | Prop | Type | Description |
276
- | --------------- | --------------------------------------------------------- | ------------------------------------------------------------------ |
277
- | **`dataType`** | <code><a href="#healthdatatype">HealthDataType</a></code> | The type of data to retrieve from the health store. |
278
- | **`startDate`** | <code>string</code> | Inclusive ISO 8601 start date (defaults to now - 1 day). |
279
- | **`endDate`** | <code>string</code> | Exclusive ISO 8601 end date (defaults to now). |
280
- | **`limit`** | <code>number</code> | Maximum number of samples to return (defaults to 100). |
281
- | **`ascending`** | <code>boolean</code> | Return results sorted ascending by start date (defaults to false). |
819
+ - Health Connect must be installed separately on devices with Android 13 or lower
820
+ - Android 14+ includes Health Connect as a system service
821
+ - Health Connect serves as a centralized hub for health data from multiple apps
822
+ - Not all Android OEMs enable Health Connect on their devices
823
+ - Users control which apps can access Health Connect through system settings
282
824
 
825
+ ---
283
826
 
284
- #### WriteSampleOptions
827
+ ## 🧪 Testing
285
828
 
286
- | Prop | Type | Description |
287
- | --------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
288
- | **`dataType`** | <code><a href="#healthdatatype">HealthDataType</a></code> | |
289
- | **`value`** | <code>number</code> | |
290
- | **`unit`** | <code><a href="#healthunit">HealthUnit</a></code> | Optional unit override. If omitted, the default unit for the data type is used (count for `steps`, meter for `distance`, kilocalorie for `calories`, bpm for `heartRate`, kilogram for `weight`, minute for `sleep`). |
291
- | **`startDate`** | <code>string</code> | ISO 8601 start date for the sample. Defaults to now. |
292
- | **`endDate`** | <code>string</code> | ISO 8601 end date for the sample. Defaults to startDate. |
293
- | **`metadata`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> | Metadata key-value pairs forwarded to the native APIs where supported. |
829
+ ### Testing on iOS
294
830
 
831
+ 1. Use a physical device (Simulator has limited HealthKit support)
832
+ 2. Generate sample health data using the Health app or third-party apps
833
+ 3. Test with Apple Watch if testing watch-specific metrics
295
834
 
296
- ### Type Aliases
835
+ ### Testing on Android
297
836
 
837
+ 1. Install Health Connect from the Play Store (if not pre-installed)
838
+ 2. Use Health Connect's test data generator or third-party health apps
839
+ 3. Test permission flows thoroughly—users can grant/deny per-data-type
840
+ 4. Test on multiple Android versions (9, 10, 13, 14) for compatibility
298
841
 
299
- #### HealthDataType
842
+ ---
300
843
 
301
- <code>'steps' | 'distance' | 'calories' | 'heartRate' | 'weight' | 'sleep'</code>
844
+ ## 🤝 Contributing
302
845
 
846
+ Contributions are welcome! Please follow these guidelines:
303
847
 
304
- #### HealthUnit
848
+ 1. Fork the repository
849
+ 2. Create a feature branch
850
+ 3. Make your changes with clear commit messages
851
+ 4. Test on both iOS and Android
852
+ 5. Submit a pull request
305
853
 
306
- <code>'count' | 'meter' | 'kilocalorie' | 'bpm' | 'kilogram' | 'minute'</code>
854
+ Please ensure your code follows the existing code style and includes appropriate error handling.
307
855
 
856
+ ---
308
857
 
309
- #### SleepState
858
+ ## 📄 License
310
859
 
311
- <code>'inBed' | 'asleep' | 'awake' | 'asleepCore' | 'asleepDeep' | 'asleepREM' | 'unknown'</code>
860
+ This project is licensed under the **MPL-2.0 License** (Mozilla Public License 2.0).
312
861
 
862
+ See the [LICENSE](LICENSE) file for details.
313
863
 
314
- #### Record
864
+ ---
315
865
 
316
- Construct a type with a set of properties K of type T
866
+ ## 🔗 Links
317
867
 
318
- <code>{
319
868
  [P in K]: T;
320
869
  }</code>
870
+ - **GitHub Repository**: [https://github.com/sandip-3008/capacitor-health](https://github.com/sandip-3008/capacitor-health)
871
+ - **npm Package**: [@interval-health/capacitor-health](https://www.npmjs.com/package/@interval-health/capacitor-health)
872
+ - **Issues & Bug Reports**: [GitHub Issues](https://github.com/sandip-3008/capacitor-health/issues)
321
873
 
322
- </docgen-api>
874
+ ---
875
+
876
+ ## 📞 Support
877
+
878
+ For questions, issues, or feature requests:
879
+
880
+ 1. Check the [documentation](#-api-reference) and [common patterns](#-common-usage-patterns)
881
+ 2. Search [existing issues](https://github.com/sandip-3008/capacitor-health/issues)
882
+ 3. Open a new issue with detailed information about your problem
883
+
884
+ **Note**: When reporting issues, please include:
885
+ - Platform (iOS/Android)
886
+ - OS version
887
+ - Plugin version
888
+ - Code sample demonstrating the issue
889
+ - Error messages or logs
890
+
891
+ ---
892
+
893
+ ## 📋 TypeScript Types
894
+
895
+ The plugin is written in TypeScript and includes full type definitions. Import types directly:
896
+
897
+ ```typescript
898
+ import {
899
+ Health,
900
+ HealthDataType,
901
+ HealthUnit,
902
+ SleepState,
903
+ AuthorizationOptions,
904
+ AuthorizationStatus,
905
+ QueryOptions,
906
+ HealthSample,
907
+ ReadSamplesResult,
908
+ WriteSampleOptions
909
+ } from '@interval-health/capacitor-health';
910
+ ```
323
911
 
324
- ### Credits:
912
+ ---
325
913
 
326
- this plugin was inspired by the work of https://github.com/perfood/capacitor-healthkit/ for ios and https://github.com/perfood/capacitor-google-fit for Android
914
+ **Made with ❤️ for the Capacitor community**