Skip to content
Document DB v7.2: Temporal Support, Telemetry Collection, All Calculations, String Based APIs, & Orleans Storage Providers! Feed The Machine Here

Reading Data

You can query health data from Apple HealthKit and Android Health Connect using the IHealthService interface. Always request read permissions first.

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 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)" : "")}");
}

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/dL
var resp = (await health.GetRespiratoryRate(start, end, Interval.Days)).Average(x => x.Value); // breaths/min
var vo2 = (await health.GetVo2Max(start, end, Interval.Days)).Average(x => x.Value); // mL/kg/min
var hrv = (await health.GetHeartRateVariability(start, end, Interval.Days)).Average(x => x.Value); // ms
var active = (await health.GetActiveEnergyBurned(start, end, Interval.Days)).Sum(x => x.Value); // kcal
var floors = (await health.GetFloorsClimbed(start, end, Interval.Days)).Sum(x => x.Value); // count

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/Unprotected
var ovulation = await health.GetOvulationTests(start, end); // .Outcome: Inconclusive/Positive/High/Negative
var mucus = await health.GetCervicalMucus(start, end); // .Appearance: Dry/Sticky/Creamy/Watery/EggWhite
var spotting = await health.GetIntermenstrualBleeding(start, end);// event-only (no value)
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");
}
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");

See the dedicated Observing Data page for streaming health data changes via IAsyncEnumerable<HealthResult>.

  1. Always request permissions first — Call RequestPermissions before reading data
  2. Use appropriate intervalsInterval.Days for summaries, Interval.Hours for detailed breakdowns
  3. Handle empty results — Check .Any() before calling .Average() to avoid InvalidOperationException
  4. Use CancellationToken — Pass cancellation tokens for long-running queries
  5. 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)
  6. Blood pressure is special — It returns BloodPressureResult (not NumericHealthResult) with separate Systolic and Diastolic values
  7. Menstruation is special — It returns MenstruationFlowResult with a categorical MenstrualFlow value and is read via GetMenstruationFlow(start, end) (no Interval)