Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Reading Data

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

Before reading or writing, gate on IsAvailable — a whole-store check. On iOS this is always true; on Android it’s true only when Health Connect is installed and the SDK status is OK.

if (!health.IsAvailable)
return; // Health Connect not installed on Android, etc.
var steps = await health.GetStepCounts(start, end, Interval.Days);
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 - the user can grant a subset, so only
// query the types that came back true
var granted = result.Where(x => x.Success).Select(x => x.Type).ToHashSet();
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. Gate on availability — Check IsAvailable before reading; it returns false for unavailable stores (e.g. Health Connect not installed on Android)
  2. Always request permissions first — Call RequestPermissions before reading data
  3. Use appropriate intervalsInterval.Days for summaries, Interval.Hours for detailed breakdowns
  4. Handle empty results — Check .Any() before calling .Average() to avoid InvalidOperationException
  5. Use CancellationToken — Pass cancellation tokens for long-running queries
  6. 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)
  7. Blood pressure is special — It returns BloodPressureResult (not NumericHealthResult) with separate Systolic and Diastolic values
  8. Menstruation is special — It returns MenstruationFlowResult with a categorical MenstrualFlow value and is read via GetMenstruationFlow(start, end) (no Interval)