Abstraction around Android Health Connect and iOS HealthKit with unified DTO-based API
Feel free to contribute ❤️
- Generic API: Use
GetHealthDataAsync<TDto>()for type-safe health data retrieval - Unified DTOs: Platform-agnostic data transfer objects with common properties
- Time Range Support: Duration-based metrics implement
IHealthTimeRangeinterface - Cross-Platform: Works with Android Health Connect and iOS HealthKit
| Health Data Type | Android Health Connect | iOS HealthKit | Wrapper Implementation |
|---|---|---|---|
| Steps | ✅ StepsRecord | ✅ StepCount | ✅ StepsDto |
| Weight | ✅ WeightRecord | ✅ BodyMass | ✅ WeightDto |
| Height | ✅ HeightRecord | ✅ Height | ✅ HeightDto |
| Heart Rate | ✅ HeartRateRecord | ✅ HeartRate | ✅ HeartRateDto |
| Active Calories | ✅ ActiveCaloriesBurnedRecord | ✅ ActiveEnergyBurned | ✅ ActiveCaloriesBurnedDto |
| Exercise Session | ✅ ExerciseSessionRecord | ✅ Workout | ✅ WorkoutDto |
| Blood Glucose | ✅ BloodGlucoseRecord | ✅ BloodGlucose | ❌ N/A |
| Body Temperature | ✅ BodyTemperatureRecord | ✅ BodyTemperature | ❌ N/A |
| Oxygen Saturation | ✅ OxygenSaturationRecord | ✅ OxygenSaturation | ❌ N/A |
| Respiratory Rate | ✅ RespiratoryRateRecord | ✅ RespiratoryRate | ❌ N/A |
| Basal Metabolic Rate | ✅ BasalMetabolicRateRecord | ✅ BasalEnergyBurned | ❌ N/A |
| Body Fat | ✅ BodyFatRecord | ✅ BodyFatPercentage | ✅ BodyFatDto |
| Lean Body Mass | ✅ LeanBodyMassRecord | ✅ LeanBodyMass | ❌ N/A |
| Hydration | ✅ HydrationRecord | ✅ DietaryWater | ❌ N/A |
| VO2 Max | ✅ Vo2MaxRecord | ✅ VO2Max | ✅ Vo2MaxDto |
| Resting Heart Rate | ✅ RestingHeartRateRecord | ✅ RestingHeartRate | ❌ N/A |
| Heart Rate Variability | ✅ HeartRateVariabilityRmssdRecord | ✅ HeartRateVariabilitySdnn | ❌ N/A |
| Blood Pressure | ✅ BloodPressureRecord | ✅ Split into Systolic/Diastolic | 🚧 WIP (commented out) |
Register the health service in your MauiProgram.cs:
builder.Services.AddHealth();Then setup all Android and iOS necessities.
- Android (4) docs, docs2
- in Google Play console give Health permissions to the app
- for successful app approval your Policy page must contain
Health data collection and use,Data retention policy - change of
AndroidManifest.xml+ new activity showing privacy policy - change of min. Android version to v26
- iOS (3) docs, docs2
- generating new provisioning profile containing HealthKit permissions. These permissions are changed in Identifiers
- adding
Entitlements.plist - adjustment of
Info.plist⚠️ Beware, if your app already exists and targets various devices addingUIRequiredDeviceCapabilitieswithhealthkitcan get your release rejected. For that reason I ommited adding this requirement and I just make sure that I check if the device is capable of usinghealthkit.
After you have everything setup correctly you can use IHealthService from DI container and call it's methods.
If you want an example there is a DemoApp project showing number of steps for Current day
public class HealthExampleService
{
private readonly IHealthService _healthService;
public HealthExampleService(IHealthService healthService)
{
_healthService = healthService;
}
public async Task<List<StepsDto>> GetTodaysStepsAsync()
{
var timeRange = HealthTimeRange.FromDateTime(DateTime.Today, DateTime.Now);
var steps = await _healthService.GetHealthDataAsync<StepsDto>(timeRange);
return steps.ToList();
}
public async Task<List<WeightDto>> GetRecentWeightAsync()
{
var timeRange = HealthTimeRange.FromDateTime(DateTime.Now.AddDays(-7), DateTime.Now);
var weights = await _healthService.GetHealthDataAsync<WeightDto>(timeRange);
return weights.ToList();
}
}Duration-based metrics implement IHealthTimeRange:
public async Task AnalyzeStepsData()
{
var timeRange = HealthTimeRange.FromDateTime(DateTime.Today, DateTime.Now);
var steps = await _healthService.GetHealthDataAsync<StepsDto>(timeRange);
foreach (var stepRecord in steps)
{
// Common properties from BaseHealthMetricDto
Console.WriteLine($"ID: {stepRecord.Id}");
Console.WriteLine($"Source: {stepRecord.DataOrigin}");
Console.WriteLine($"Recorded: {stepRecord.Timestamp}");
// Steps-specific data
Console.WriteLine($"Steps: {stepRecord.Count}");
// Time range data (IHealthTimeRange)
Console.WriteLine($"Period: {stepRecord.StartTime} to {stepRecord.EndTime}");
Console.WriteLine($"Duration: {stepRecord.Duration}");
// Type-safe duration checking
if (stepRecord is IHealthTimeRange timeRange)
{
Console.WriteLine($"This measurement lasted {timeRange.Duration.TotalMinutes} minutes");
}
}
}
public async Task AnalyzeWeightData()
{
var timeRange = HealthTimeRange.FromDateTime(DateTime.Today.AddDays(-30), DateTime.Now);
var weights = await _healthService.GetHealthDataAsync<WeightDto>(timeRange);
foreach (var weightRecord in weights)
{
// Instant measurements only have Timestamp
Console.WriteLine($"Weight: {weightRecord.Value} {weightRecord.Unit}");
Console.WriteLine($"Measured at: {weightRecord.Timestamp}");
Console.WriteLine($"Source: {weightRecord.DataOrigin}");
}
}public async Task RequestPermissions()
{
var permissions = new List<HealthPermissionDto>
{
new() { HealthDataType = HealthDataType.Steps, PermissionType = PermissionType.Read },
new() { HealthDataType = HealthDataType.Weight, PermissionType = PermissionType.Read },
new() { HealthDataType = HealthDataType.Height, PermissionType = PermissionType.Read }
};
var result = await _healthService.RequestPermissions(permissions);
if (result.IsSuccess)
{
Console.WriteLine("Permissions granted!");
}
else
{
Console.WriteLine($"Permission error: {result.Error}");
}
}iOS Simulator/Device:
- If no health data exists, open the Health app
- Navigate to the desired metric (e.g., Steps)
- Tap "Add Data" in the top-right corner
- Manually add test data for development
Android Emulator:
- Install Google Health Connect app
- Add sample health data for testing
- Ensure proper permissions are granted
- @aritchie -
https://github.com/shinyorg/Health - @0xc3u -
https://github.com/0xc3u/Plugin.Maui.Health - @EagleDelux -
https://github.com/EagleDelux/androidx.health-connect-demo-.net-maui - @b099l3 -
https://github.com/b099l3/ios-samples/tree/65a4ab1606cfd8beb518731075e4af526c4da4ad/ios8/Fit/Fit
