Reading Data
You can query health data from Apple HealthKit and Android Health Connect using the IHealthService interface. Always request read permissions first.
Querying Health Data
Section titled “Querying Health Data”public class HealthDashboardViewModel(IHealthService health){ async Task LoadDataAsync() { // 1. Request permissions (read-only shorthand) var result = await health.RequestPermissions( DataType.StepCount, DataType.HeartRate, DataType.Calories, DataType.Distance );
// Or request per-metric read/write permissions in a single call // await health.RequestPermissions( // (PermissionType.Read, DataType.StepCount), // (PermissionType.Read, DataType.HeartRate), // (PermissionType.Write, DataType.Weight), // (PermissionType.ReadWrite, DataType.BloodPressure) // );
// 2. Check which permissions were granted foreach (var (type, success) in result) { if (!success) Console.WriteLine($"Permission denied for {type}"); }
// 3. Query data for the last 24 hours var end = DateTimeOffset.Now; var start = end.AddDays(-1);
var steps = (await health.GetStepCounts(start, end, Interval.Days)).Sum(x => x.Value); var calories = (await health.GetCalories(start, end, Interval.Days)).Sum(x => x.Value); var distance = (await health.GetDistances(start, end, Interval.Days)).Sum(x => x.Value); var heartRate = (await health.GetAverageHeartRate(start, end, Interval.Days)).Average(x => x.Value);
// 4. Blood pressure returns a special result type var bp = await health.GetBloodPressure(start, end, Interval.Days); if (bp.Any()) { var avgSystolic = bp.Average(x => x.Systolic); var avgDiastolic = bp.Average(x => x.Diastolic); }
// 5. Hourly breakdown var hourlySteps = await health.GetStepCounts(start, end, Interval.Hours); foreach (var bucket in hourlySteps) { Console.WriteLine($"{bucket.Start:g} - {bucket.End:g}: {bucket.Value:N0} steps"); } }}Menstruation / Period Tracking
Section titled “Menstruation / Period Tracking”Menstruation flow is categorical and event-based, so it does not use Interval bucketing. Read the raw records over a date range with GetMenstruationFlow:
await health.RequestPermissions(DataType.MenstruationFlow);
var end = DateTimeOffset.Now;var start = end.AddMonths(-1);
var records = await health.GetMenstruationFlow(start, end);foreach (var r in records){ // r.Flow is a MenstrualFlow enum: Unspecified, None, Light, Medium, Heavy Console.WriteLine($"{r.Start:d}: {r.Flow}{(r.IsCycleStart ? " (cycle start)" : "")}");}More Numeric Metrics
Section titled “More Numeric Metrics”Beyond the core activity/body/vitals metrics, the same interval-bucketed pattern works for blood glucose, body temperature, respiratory rate, VO2 max, heart rate variability, lean body mass, active/basal energy, floors climbed, wheelchair pushes, speed, and power:
await health.RequestPermissions( DataType.BloodGlucose, DataType.RespiratoryRate, DataType.Vo2Max, DataType.HeartRateVariability, DataType.ActiveEnergyBurned, DataType.FloorsClimbed);
var end = DateTimeOffset.Now;var start = end.AddDays(-1);
var glucose = (await health.GetBloodGlucose(start, end, Interval.Days)).Average(x => x.Value); // mg/dLvar resp = (await health.GetRespiratoryRate(start, end, Interval.Days)).Average(x => x.Value); // breaths/minvar vo2 = (await health.GetVo2Max(start, end, Interval.Days)).Average(x => x.Value); // mL/kg/minvar hrv = (await health.GetHeartRateVariability(start, end, Interval.Days)).Average(x => x.Value); // msvar active = (await health.GetActiveEnergyBurned(start, end, Interval.Days)).Sum(x => x.Value); // kcalvar floors = (await health.GetFloorsClimbed(start, end, Interval.Days)).Sum(x => x.Value); // countReproductive & Cycle Tracking
Section titled “Reproductive & Cycle Tracking”Sexual activity, ovulation tests, cervical mucus, and intermenstrual bleeding are event-based (like menstruation flow) — read the raw records over a date range, no Interval:
await health.RequestPermissions( DataType.SexualActivity, DataType.OvulationTest, DataType.CervicalMucus, DataType.IntermenstrualBleeding);
var sexual = await health.GetSexualActivity(start, end); // .Protection: Unspecified/Protected/Unprotectedvar ovulation = await health.GetOvulationTests(start, end); // .Outcome: Inconclusive/Positive/High/Negativevar mucus = await health.GetCervicalMucus(start, end); // .Appearance: Dry/Sticky/Creamy/Watery/EggWhitevar spotting = await health.GetIntermenstrualBleeding(start, end);// event-only (no value)Workouts
Section titled “Workouts”await health.RequestPermissions(DataType.Workout);
var workouts = await health.GetWorkouts(start, end);foreach (var w in workouts){ // w.Workout is a WorkoutType (Running, Cycling, Swimming, …) Console.WriteLine($"{w.Workout}: {w.TotalEnergyKilocalories} kcal, {w.TotalDistanceMeters} m");}Nutrition
Section titled “Nutrition”await health.RequestPermissions(DataType.Nutrition);
var meals = await health.GetNutrition(start, end);foreach (var m in meals) Console.WriteLine($"{m.Meal} {m.Name}: {m.EnergyKilocalories} kcal, {m.ProteinGrams}g protein");Real-Time Observation
Section titled “Real-Time Observation”See the dedicated Observing Data page for streaming health data changes via IAsyncEnumerable<HealthResult>.
Best Practices
Section titled “Best Practices”- Always request permissions first — Call
RequestPermissionsbefore reading data - Use appropriate intervals —
Interval.Daysfor summaries,Interval.Hoursfor detailed breakdowns - Handle empty results — Check
.Any()before calling.Average()to avoidInvalidOperationException - Use CancellationToken — Pass cancellation tokens for long-running queries
- Sum vs Average — Use
.Sum()for cumulative metrics (steps, calories, distance, hydration, sleep) and.Average()for point-in-time metrics (heart rate, weight, height, body fat, O2 sat, resting HR) - Blood pressure is special — It returns
BloodPressureResult(notNumericHealthResult) with separateSystolicandDiastolicvalues - Menstruation is special — It returns
MenstruationFlowResultwith a categoricalMenstrualFlowvalue and is read viaGetMenstruationFlow(start, end)(noInterval)