Open Wearables Health SDK
A Flutter plugin for secure background health data synchronization from Apple HealthKit (iOS) to your backend.
Part of Open Wearables - a self-hosted platform to unify wearable health data through one AI-ready API.
Features
- 🔐 Token Authentication - Sign in with accessToken + refreshToken, SDK handles refresh automatically
- 📱 Background Sync - Health data syncs even when app is in background
- 📦 Incremental Updates - Only syncs new data using anchored queries
- 💾 Secure Storage - Credentials stored in iOS Keychain
- 📊 Wide Data Support - Steps, heart rate, workouts, sleep, and more
- 🌐 Custom Host - Point the SDK at any compatible backend
Installation
1. Add Dependency
dependencies:
open_wearables_health_sdk: ^0.1.0
2. iOS Configuration
Add to Info.plist:
<key>NSHealthShareUsageDescription</key>
<string>This app syncs your health data to your account.</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.openwearables.healthsdk.task.refresh</string>
<string>com.openwearables.healthsdk.task.process</string>
</array>
Enable HealthKit in Xcode → Target → Signing & Capabilities → + HealthKit.
SDK Usage
1. Configure (once at app start)
The host parameter is required — provide just the host URL, the SDK appends /api/v1/... paths automatically.
await OpenWearablesHealthSdk.configure(
host: 'https://api.example.com',
);
// Session is automatically restored if user was previously signed in
if (OpenWearablesHealthSdk.isSignedIn) {
print('Welcome back, ${OpenWearablesHealthSdk.currentUser?.userId}!');
}
2. Sign In
// Sign in with tokens (supports automatic token refresh)
try {
final user = await OpenWearablesHealthSdk.signIn(
userId: 'user-id',
accessToken: 'Bearer access-token',
refreshToken: 'refresh-token',
);
print('Connected: ${user.userId}');
} on SignInException catch (e) {
print('Failed: ${e.message}');
}
// Or with API key (simpler, no automatic token refresh):
final user = await OpenWearablesHealthSdk.signIn(
userId: 'your-user-id',
apiKey: 'your-api-key',
);
3. Request Permissions
final authorized = await OpenWearablesHealthSdk.requestAuthorization(
types: [
HealthDataType.steps,
HealthDataType.heartRate,
HealthDataType.sleep,
HealthDataType.workout,
],
);
4. Start Background Sync
await OpenWearablesHealthSdk.startBackgroundSync();
5. Check Sync Status (optional)
final status = await OpenWearablesHealthSdk.getSyncStatus();
if (status['hasResumableSession'] == true) {
print('Sync interrupted, ${status['sentCount']} records already sent');
// Manually resume if needed
await OpenWearablesHealthSdk.resumeSync();
}
6. Sign Out
await OpenWearablesHealthSdk.signOut();
// All credentials cleared from Keychain
URL Structure
When you provide a host (e.g. https://api.example.com), the SDK constructs all endpoints automatically:
| Endpoint | URL |
|---|---|
| Health data sync | {host}/api/v1/sdk/users/{userId}/sync/apple |
| Token refresh | {host}/api/v1/token/refresh |
Complete Example
class HealthService {
final String host;
HealthService({required this.host});
Future<void> connect({
required String userId,
required String accessToken,
required String refreshToken,
}) async {
// 1. Configure SDK with your host
await OpenWearablesHealthSdk.configure(host: host);
// 2. Check current status
if (OpenWearablesHealthSdk.isSignedIn) {
// Already signed in, just start sync if needed
if (!OpenWearablesHealthSdk.isSyncActive) {
await _startSync();
}
return;
}
// 3. Sign in with credentials
await OpenWearablesHealthSdk.signIn(
userId: userId,
accessToken: accessToken,
refreshToken: refreshToken,
);
// 4. Start syncing
await _startSync();
}
Future<void> _startSync() async {
await OpenWearablesHealthSdk.requestAuthorization(
types: HealthDataType.values,
);
await OpenWearablesHealthSdk.startBackgroundSync();
}
Future<void> disconnect() async {
await OpenWearablesHealthSdk.stopBackgroundSync();
await OpenWearablesHealthSdk.signOut();
}
}
Example App
The example app demonstrates the full flow using an invitation code:
- Enter the Host URL and an Invitation Code
- The app redeems the code at
{host}/api/v1/invitation-code/redeem - Receives
access_token,refresh_token, anduser_id - Signs in with the SDK and starts syncing
- Session auto-restores on app restart — no need to re-enter the code
Supported Health Data Types
| Category | Types |
|---|---|
| Activity | steps, distanceWalkingRunning, distanceCycling, flightsClimbed, walkingSpeed, walkingStepLength, walkingAsymmetryPercentage, walkingDoubleSupportPercentage, sixMinuteWalkTestDistance |
| Energy | activeEnergy, basalEnergy |
| Heart | heartRate, restingHeartRate, heartRateVariabilitySDNN, vo2Max, oxygenSaturation |
| Respiratory | respiratoryRate |
| Body | bodyMass, height, bmi, bodyFatPercentage, leanBodyMass, waistCircumference (iOS 16+), bodyTemperature |
| Blood Glucose / Insulin | bloodGlucose, insulinDelivery (iOS 16+) |
| Blood Pressure | bloodPressure, bloodPressureSystolic, bloodPressureDiastolic |
| Nutrition | dietaryEnergyConsumed, dietaryCarbohydrates, dietaryProtein, dietaryFatTotal, dietaryWater |
| Sleep | sleep, mindfulSession |
| Reproductive | menstrualFlow, cervicalMucusQuality, ovulationTestResult, sexualActivity |
| Workouts | workout |
API Reference
OpenWearablesHealthSdk
| Method | Description |
|---|---|
configure({required host}) |
Initialize SDK with host URL and restore session |
signIn({userId, accessToken?, refreshToken?, apiKey?}) |
Sign in with tokens or API key |
signOut() |
Sign out and clear all credentials |
updateTokens({accessToken, refreshToken?}) |
Update tokens without re-signing in |
requestAuthorization({types}) |
Request health data permissions |
startBackgroundSync() |
Enable background sync |
stopBackgroundSync() |
Disable background sync |
syncNow() |
Trigger immediate sync |
resetAnchors() |
Reset sync state (forces full re-export) |
getStoredCredentials() |
Get stored credentials for debugging |
getSyncStatus() |
Get current sync session status |
resumeSync() |
Manually resume interrupted sync |
clearSyncSession() |
Clear interrupted sync without resuming |
Properties
| Property | Type | Description |
|---|---|---|
isConfigured |
bool |
SDK is configured |
isSignedIn |
bool |
User is signed in |
isSyncActive |
bool |
Background sync is active |
currentUser |
OpenWearablesHealthSdkUser? |
Current user info |
config |
OpenWearablesHealthSdkConfig? |
Current configuration |
status |
OpenWearablesHealthSdkStatus |
Current SDK status |
OpenWearablesHealthSdkStatus
| Status | Description |
|---|---|
notConfigured |
SDK not configured, call configure() |
configured |
SDK configured, but no user signed in |
signedIn |
User signed in, ready to sync |
getSyncStatus() Return Values
| Key | Type | Description |
|---|---|---|
hasResumableSession |
bool |
Whether there's an interrupted sync to resume |
sentCount |
int |
Number of records already sent in this session |
isFullExport |
bool |
Whether this is a full export or incremental sync |
createdAt |
String? |
ISO8601 timestamp when sync started |
Exceptions
| Exception | When Thrown |
|---|---|
NotConfiguredException |
configure() was not called |
NotSignedInException |
No user signed in |
SignInException |
Sign-in failed |
License
MIT License