Skip to main content
synheart-wear ships a stable adapter layer per vendor. This page is the reference for what each adapter does, what it produces, and how it interacts with cloud streaming.

Adapter matrix

The DeviceAdapter enums differ per SDK:
  • Dart: platformHealth, fitbit, garmin, whoop, samsungHealth (no adapter behind it yet). Oura is reached through OuraProvider and BLE HRM through BleHrmProvider, not the enum.
  • Kotlin: HEALTH_CONNECT, SAMSUNG_HEALTH, FITBIT, GARMIN, OURA, WHOOP, BLE_HRM.
  • Swift: platformHealth, fitbit, garmin, oura, whoop, bleHrm.

Apple HealthKit

Capabilities:
  • HR (instantaneous + samples).
  • HRV: HealthKit gives SDNN, stored as hrv_sdnn only (Flutter releases before 0.7.0 also copied it into hrv_rmssd).
  • Steps, calories, distance.
  • RR intervals via the HealthKit RR channel (workout-mode only on most Apple Watch models).
  • Health profile snapshot (DOB, sex, height, weight) for personalization context.
  • Workout events.
Permissions: standard HealthKit HKObjectType set, requested once via requestPermissions. source field on WearMetrics: "apple_healthkit".

Health Connect (Android)

Capabilities:
  • HR, HRV (when the recording app provides it; otherwise SDNN/RMSSD are unavailable).
  • Steps, calories, distance.
  • Sleep records (sleep stages from compatible apps).
Health Connect lets an app read only the 30 days before it was first granted access, for every record type, unless it also holds READ_HEALTH_DATA_HISTORY. See Android history window. source field: "health_connect". On Flutter (0.7.0+), data read through a SynheartWear instance is labelled "health_connect" with a health_connect_… device id; the static HealthAdapter reads are labelled "health_package". Flutter releases before 0.7.0 labelled Health Connect data "apple_healthkit".

BLE HRM

Direct connection over BLE GATT Heart Rate Profile (0x180D). No vendor cloud, no OAuth. Compatible devices:
  • WHOOP (Broadcast HR mode).
  • Polar chest straps (H10, OH1).
  • Wahoo TICKR family.
  • Garmin HRM-Pro / HRM-Dual.
  • Any HRM advertising 0x180D Heart Rate Service.
Capabilities:
  • HR (every second per Bluetooth profile).
  • RR intervals when the device advertises the optional RR field.
The bridge runs its own permission/scan/connect/disconnect lifecycle independent of requestPermissions. See BLE Heart Rate Monitor for the detailed flow. source field: "ble_hrm".

WHOOP

Two modes: WhoopProvider exposes:
  • fetchRecovery, fetchSleep, fetchWorkouts, fetchCycles β€” date-range record queries (Flutter also has fetchRawData).
  • Real-time HR via webhooks (mode A) or BLE (mode B).
  • Workout events.
source field: "whoop" for the OAuth path. The cloud-vs-BLE distinction lives in meta.source_type ("whoop_cloud"); WHOOP via BLE Broadcast HR uses the shared BLE HRM adapter (source = "ble_hrm").

Garmin (Cloud)

Path: OAuth β†’ Garmin Connect API webhooks β†’ cloud β†’ RAMEN. Delivery hint: ping β€” vendor only sends a notification; client must pull the full record via REST when needed. GarminProvider exposes:
  • Backfill queries for the Garmin Health API summary types: fetchDailies, fetchEpochs, fetchSleeps, fetchStressDetails, fetchHRV, fetchUserMetrics, fetchBodyComps, fetchPulseOx, fetchRespiration, fetchHealthSnapshot, fetchBloodPressures, fetchSkinTemp (Flutter also has fetchActivities). Each returns WearMetrics; on Flutter the vendor record is kept in meta.garmin_data.
The GarminWellnessData, GarminSleepData, GarminConnectionState and GarminRealtimeData models belong to the GarminHealth (RTS) layer below, not to GarminProvider. source field: "garmin" for the OAuth Connect API path; meta.source_type carries the more specific "garmin_cloud".

Garmin Health SDK (Real-Time Streaming)

Native RTS path β€” direct device pairing without going through Garmin Connect cloud. Requires a separate Garmin license and is bundled on demand for licensed integrations only. The underlying Garmin Health SDK code is proprietary; this SDK exposes a GarminHealth provider faΓ§ade that wraps it. source field: "garmin_sdk" (the adapter id from GarminSdkAdapter).

Fitbit

OAuth flow mediated by the Synheart Wear API β€” the device never sees Fitbit access tokens. Cloud delivers a vendor_user_id to the app via deep-link callback (?vendor=fitbit&user_id=...&status=success); pass that as the code argument to connectWithCode(...). FitbitProvider exposes:
  • fetchHrv, fetchSleep, fetchActivity β€” date-range record queries.
  • fetchUserProfile β€” Fitbit profile snapshot (Flutter only).
source field: "fitbit".

Oura

OuraProvider (all three SDKs) uses mediated OAuth via the Synheart Wear API, with the same vendor_user_id-via-deep-link contract as Fitbit. OuraProvider exposes fetchReadiness, fetchSleep, fetchHrv, fetchActivity (Flutter also has fetchUserProfile). source field: "oura".

Streaming model

Today, cloud providers (Whoop, Garmin, Fitbit, Oura) are polled: streamHR / streamHRV run a timer (Combine Timer.publish on Swift, kotlinx.coroutines.flow.flow { delay(...) } on Kotlin) that calls readMetrics(isRealTime: true) on each tick. There is no push channel for these vendors yet. BleHrmProvider is push-based β€” the BLE characteristic notification feeds a SharedFlow / AsyncStream. On Kotlin and Swift, readMetrics and streamHR include the latest BLE sample. On Flutter they don’t: listen to BleHrmProvider.onHeartRate directly. Future work: a RamenEventDispatcher on Kotlin / Swift (Flutter already has one) will materialize cloud-side ping events into REST pull-backs, replacing the timer-based polling for cloud providers.

Apple Health XML backfill

When a user exports export.zip from the iOS Health app:
Idempotency keys ensure repeated imports don’t duplicate samples in the runtime’s SQLite store. The high-level path through synheart-core aggregates samples per day and replays them via Synheart.srmPushWearableDaily(...) so the imported data populates the user’s runtime baselines.

RAMEN 🍜 integration

Each cloud adapter participates in the RAMEN stream when Synheart’s cloud ingestion publishes events:
Each adapter handler decides how to consume RamenEvent.payload_json:
  • DeliveryHint.stream: parse inline, emit immediately.
  • DeliveryHint.ping: schedule a REST pull (via the adapter’s provider) to fetch the body.
  • DeliveryHint.unknown: treat as stream (best-effort) and log.

Permission flow

SynheartWear.requestPermissions(...) maps Synheart PermissionType values to OS permission scopes for the platform health store: BLE HRM (Bluetooth prompts) and cloud OAuth (app navigation) handle their own UX outside this method.