@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 +781 -193
- package/android/src/main/java/app/capgo/plugin/health/HealthDataType.kt +4 -1
- package/dist/docs.json +24 -0
- package/dist/esm/definitions.d.ts +20 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/HealthPlugin/Health.swift +1921 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,324 +1,912 @@
|
|
|
1
|
-
# @
|
|
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
|
-
|
|
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
|
-
|
|
5
|
+
---
|
|
10
6
|
|
|
11
|
-
##
|
|
7
|
+
## 📦 Installation
|
|
12
8
|
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
+
## 🚀 Supported Platforms
|
|
24
22
|
|
|
25
|
-
|
|
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
|
-
|
|
29
|
+
### Platform Requirements
|
|
28
30
|
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
42
|
+
---
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
52
|
+
<string>This app needs access to read your health data.</string>
|
|
44
53
|
<key>NSHealthUpdateUsageDescription</key>
|
|
45
|
-
<string>This app
|
|
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
|
-
|
|
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
|
-
|
|
100
|
+
---
|
|
51
101
|
|
|
52
|
-
|
|
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
|
-
|
|
104
|
+
### Import the Plugin
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { Health } from '@interval-health/capacitor-health';
|
|
108
|
+
```
|
|
58
109
|
|
|
59
|
-
|
|
110
|
+
### Complete Workflow Example
|
|
60
111
|
|
|
61
|
-
|
|
62
|
-
import { Health } from '@capgo/capacitor-health';
|
|
112
|
+
Here's a typical flow for working with health data:
|
|
63
113
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
await Health.requestAuthorization({
|
|
72
|
-
read: ['steps', 'heartRate', 'weight', 'sleep'],
|
|
73
|
-
write: ['weight', 'sleep'],
|
|
74
|
-
});
|
|
157
|
+
---
|
|
75
158
|
|
|
76
|
-
|
|
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
|
-
|
|
85
|
-
await Health.saveSample({
|
|
86
|
-
dataType: 'weight',
|
|
87
|
-
value: 74.3,
|
|
88
|
-
});
|
|
161
|
+
### Health Data Types
|
|
89
162
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 🔧 Methods
|
|
266
|
+
|
|
267
|
+
### isAvailable()
|
|
268
|
+
|
|
269
|
+
Check if health services are available on the current platform.
|
|
100
270
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
275
|
+
**Returns**: `Promise<AvailabilityResult>`
|
|
111
276
|
|
|
112
|
-
|
|
277
|
+
**Example**:
|
|
278
|
+
```typescript
|
|
279
|
+
const result = await Health.isAvailable();
|
|
113
280
|
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
295
|
+
// When unavailable:
|
|
296
|
+
{
|
|
297
|
+
available: false,
|
|
298
|
+
platform: 'android',
|
|
299
|
+
reason: 'Health Connect is unavailable on this device.'
|
|
300
|
+
}
|
|
301
|
+
```
|
|
126
302
|
|
|
127
|
-
|
|
128
|
-
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
303
|
+
---
|
|
129
304
|
|
|
130
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
**Response Example**:
|
|
333
|
+
```typescript
|
|
334
|
+
{
|
|
335
|
+
readAuthorized: ['steps', 'heartRate'],
|
|
336
|
+
readDenied: ['sleep'],
|
|
337
|
+
writeAuthorized: ['steps', 'weight'],
|
|
338
|
+
writeDenied: []
|
|
339
|
+
}
|
|
340
|
+
```
|
|
137
341
|
|
|
138
|
-
**
|
|
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
|
-
|
|
351
|
+
Check the current authorization status without prompting the user.
|
|
144
352
|
|
|
145
353
|
```typescript
|
|
146
|
-
|
|
354
|
+
Health.checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>
|
|
147
355
|
```
|
|
148
356
|
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
368
|
+
if (status.readAuthorized.includes('steps')) {
|
|
369
|
+
// We have permission to read steps
|
|
370
|
+
await readStepsData();
|
|
371
|
+
}
|
|
372
|
+
```
|
|
154
373
|
|
|
155
|
-
**
|
|
374
|
+
**Response Example**: Same structure as `requestAuthorization()`
|
|
156
375
|
|
|
157
|
-
|
|
376
|
+
---
|
|
158
377
|
|
|
378
|
+
### readSamples()
|
|
159
379
|
|
|
160
|
-
|
|
380
|
+
Read health samples for a specific data type within a time range.
|
|
161
381
|
|
|
162
382
|
```typescript
|
|
163
|
-
|
|
383
|
+
Health.readSamples(options: QueryOptions): Promise<ReadSamplesResult>
|
|
164
384
|
```
|
|
165
385
|
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
468
|
+
---
|
|
184
469
|
|
|
185
|
-
|
|
186
|
-
| ------------- | ----------------------------------------------------- |
|
|
187
|
-
| **`options`** | <code><a href="#queryoptions">QueryOptions</a></code> |
|
|
470
|
+
### saveSample()
|
|
188
471
|
|
|
189
|
-
|
|
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
|
-
|
|
486
|
+
**Returns**: `Promise<void>`
|
|
195
487
|
|
|
488
|
+
**Example - Save Weight**:
|
|
196
489
|
```typescript
|
|
197
|
-
saveSample(
|
|
490
|
+
await Health.saveSample({
|
|
491
|
+
dataType: 'weight',
|
|
492
|
+
value: 72.5,
|
|
493
|
+
startDate: new Date().toISOString()
|
|
494
|
+
});
|
|
198
495
|
```
|
|
199
496
|
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
754
|
+
---
|
|
218
755
|
|
|
219
|
-
|
|
756
|
+
## 🔒 Privacy & Security
|
|
220
757
|
|
|
758
|
+
### iOS Privacy Considerations
|
|
221
759
|
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
780
|
+
---
|
|
235
781
|
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
798
|
+
### General Limitations
|
|
253
799
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
808
|
+
## 📱 Platform-Specific Notes
|
|
260
809
|
|
|
261
|
-
|
|
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
|
-
|
|
817
|
+
### Android (Health Connect)
|
|
274
818
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
827
|
+
## 🧪 Testing
|
|
285
828
|
|
|
286
|
-
|
|
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><string, string></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
|
-
###
|
|
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
|
-
|
|
842
|
+
---
|
|
300
843
|
|
|
301
|
-
|
|
844
|
+
## 🤝 Contributing
|
|
302
845
|
|
|
846
|
+
Contributions are welcome! Please follow these guidelines:
|
|
303
847
|
|
|
304
|
-
|
|
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
|
-
|
|
854
|
+
Please ensure your code follows the existing code style and includes appropriate error handling.
|
|
307
855
|
|
|
856
|
+
---
|
|
308
857
|
|
|
309
|
-
|
|
858
|
+
## 📄 License
|
|
310
859
|
|
|
311
|
-
|
|
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
|
-
|
|
864
|
+
---
|
|
315
865
|
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
912
|
+
---
|
|
325
913
|
|
|
326
|
-
|
|
914
|
+
**Made with ❤️ for the Capacitor community**
|