Health Connect to FHIR
Read Android Health Connect data from Fitbit, Garmin, Oura, Withings and Wear OS in Flutter and write it to a FHIR R4 record: permissions, history, Play review.
Health Connect is Android's on-device health store: Samsung Health, Fitbit, Garmin Connect, Oura, Withings and Wear OS apps write into it, and your app reads from it with the person's permission. To put that data in a FHIR record, read daily totals and averages on the phone, send them to your backend, and write each metric as an Observation with a LOINC code and a UCUM unit. This page covers the permissions Android requires, a Flutter example (checked with flutter analyze) that reads Health Connect on Android and Apple Health on iOS with the same code, and the rules for publishing on Google Play.
What reaches Health Connect
Health Connect is built into Android 14 and later and available as an app on older versions. It holds records such as HeartRateRecord, RestingHeartRateRecord, HeartRateVariabilityRmssdRecord, OxygenSaturationRecord, RespiratoryRateRecord, StepsRecord, DistanceRecord, ActiveCaloriesBurnedRecord, SleepSessionRecord, WeightRecord, BloodPressureRecord, BloodGlucoseRecord, BodyTemperatureRecord and ExerciseSessionRecord.
Who writes there, according to each vendor's documentation:
| Source | What to expect |
|---|---|
| Fitbit and Pixel Watch | The Fitbit app syncs with Health Connect once the person sets it up (Google help) |
| Garmin | Garmin Connect supports Health Connect on Android 14 and later: activity and wellness data such as heart rate and calories (Android Developers, August 2025) |
| Oura | Heart rate, HRV, sleep with stages, steps, distance, calories and workouts (Oura) |
| Withings | Health Mate exchanges activity, weight, sleep and other data through Health Connect (Android Developers blog) |
| Samsung Health, Galaxy Watch | A subset, at Samsung Health's own pace — see Samsung Health to FHIR |
Each vendor app must have permission to write to Health Connect, which the person grants in that app or in Health Connect's settings. If a source is missing, that is the first thing to check.
Permissions Android requires
- One permission per record type and direction, for example
android.permission.health.READ_HEART_RATEandandroid.permission.health.READ_STEPS, declared in the manifest and requested at runtime. - History: without
android.permission.health.READ_HEALTH_DATA_HISTORY, an app can read only the last 30 days before the permission was granted. - Background: reading while the app is not in the foreground needs
android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND. - A permissions rationale screen (an activity that handles
ACTION_SHOW_PERMISSIONS_RATIONALE, plus an activity alias on Android 14) that explains your privacy policy; without it the permission dialog does not open. - In Flutter, the
MainActivitymust extendFlutterFragmentActivity, notFlutterActivity, or permission requests fail.
Reading daily values in Flutter
The health package reads Health Connect on Android and Apple Health on iOS behind one API. The example uses version 13.2.1, the one Anpheros Daily ships with:
import 'dart:math' as math;
import 'package:health/health.dart';
/// One day of data from Health Connect (Android) or Apple Health (iOS), ready for your backend.
class DailyValue {
DailyValue(this.metric, this.day, this.value, {this.min, this.max});
final String metric; // 'heart_rate', 'steps'
final String day; // '2026-10-01', the user's local day
final double value;
final double? min, max;
Map<String, Object?> toJson() => {'metric': metric, 'day': day, 'value': value, 'min': min, 'max': max};
}
final health = Health();
Future<List<DailyValue>> collectLastDays({int days = 30}) async {
await health.configure();
if (!await health.isHealthConnectAvailable()) {
await health.installHealthConnect(); // opens the Play Store on Android; always available on iOS
return [];
}
const types = [HealthDataType.HEART_RATE, HealthDataType.STEPS];
final granted = await health.requestAuthorization(types, permissions: [HealthDataAccess.READ, HealthDataAccess.READ]);
if (!granted) return [];
final now = DateTime.now();
final out = <DailyValue>[];
for (var i = days - 1; i >= 0; i--) {
final start = DateTime(now.year, now.month, now.day - i); // local midnight, safe across DST changes
final end = DateTime(start.year, start.month, start.day + 1);
final day = start.toIso8601String().substring(0, 10);
final steps = await health.getTotalStepsInInterval(start, end);
if (steps != null) out.add(DailyValue('steps', day, steps.toDouble()));
final samples = await health.getHealthDataFromTypes(types: [HealthDataType.HEART_RATE], startTime: start, endTime: end);
final bpm = [for (final p in samples) if (p.value is NumericHealthValue) (p.value as NumericHealthValue).numericValue.toDouble()];
if (bpm.isNotEmpty) {
out.add(DailyValue('heart_rate', day, bpm.reduce((a, b) => a + b) / bpm.length,
min: bpm.reduce(math.min), max: bpm.reduce(math.max)));
}
}
return out; // POST these to your backend, which writes them to the FHIR record
}
getTotalStepsInInterval asks Health Connect for its aggregated total, which already resolves overlapping sources (a phone and a watch counting the same walk) according to the data-source priority the person set in Health Connect. Summing raw step records yourself double-counts.
Notes from shipping this in Anpheros Daily:
- Sync when the app comes to the foreground, with a short guard against repeated syncs. Other apps write to Health Connect on their own schedule, so the data a person sees in their vendor app may arrive minutes or hours later.
- Heart rate is a series, not one value per sample: summarise it on the phone (daily, or in 4-hour windows for charts) instead of uploading every reading.
- HRV here is RMSSD. Do not store it under the SDNN code used for Apple Health.
From Flutter values to FHIR Observations
Send the values to your backend and write one Observation per person, metric and day with a stable identifier, updating it when the same day is sent again. The full mapping table (Health Connect record → LOINC → UCUM), a validated FHIR example and the TypeScript upsert run against the platform are on Wearable data in FHIR.
Publishing on Google Play
Apps that read Health Connect must complete the Health Connect permissions declaration in the Play Console, justify every data type, and have a privacy policy that matches. Requests for data types the app does not visibly use are rejected. Plan for the review: list each type next to the screen that uses it.
Wear OS
On a Wear OS watch, an app can read live sensor data through Health Services (heart rate during a workout, for example). Data the watch's own health app records reaches the phone's Health Connect through the vendor's phone app. For a medical record, reading Health Connect on the phone is usually enough; a watch app is for live features.
Where Anpheros fits
The Anpheros Platform stores the result: the person's FHIR R4 record with each wearable value as an Observation marked as device data, behind consent, provenance and an access log. It does not read Health Connect itself; your Android app does. Anpheros' own app, Anpheros Daily, reads Health Connect on Android (Plus and Max plans) and writes daily summaries to the record.
Frequently asked questions
Does Health Connect have a cloud API?
No. Health Connect stores data on the phone, and only apps on that phone can read it, with the person's permission. To get the data to a server, your app reads it and sends it.
Why can my app only read 30 days of Health Connect data?
Without the READ_HEALTH_DATA_HISTORY permission, Health Connect limits reads to 30 days before the first permission was granted. Request the history permission if you need older data.
Does Garmin sync with Health Connect?
Yes. Google announced in August 2025 that Garmin Connect supports Health Connect on Android 14 and later, for activity and wellness data such as heart rate and calories. Garmin's own scores, such as Body Battery, are not shared.
Can I read Fitbit data without the Fitbit Web API?
On Android, yes: once the person enables Health Connect in the Fitbit app, Fitbit data is available in Health Connect for apps they authorise.
Why does the Health Connect step count differ from my vendor app?
The vendor app may not have exported its latest data yet, may export only some types, or Health Connect may prefer another source for the same period. Check the data sources and their priority in Health Connect's settings.
Related
- Wearable data in FHIR: the mapping table and the backend code
- Samsung Health to FHIR
- Apple Health (HealthKit) to FHIR
- How to build a healthcare app with Flutter