Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More
Observing Data
You can observe health data changes in real time using the Observe method on IHealthService. It returns an IAsyncEnumerable<HealthResult> that yields new samples as they are recorded.
How It Works
Section titled “How It Works”- iOS: Push-based using
HKAnchoredObjectQuery— HealthKit notifies your app immediately when new samples are added - Android: Polling-based using Health Connect change tokens — polls for new records at a configurable interval (default: 5 seconds)
- Forward-only: Only yields samples added after observation starts, not historical data
Basic Usage
Section titled “Basic Usage”IHealthService health; // inject via DI
// Request permissions firstawait health.RequestPermissions(DataType.HeartRate);
// Observe heart rate changesusing var cts = new CancellationTokenSource();
await foreach (var result in health.Observe(DataType.HeartRate, cancelToken: cts.Token)){ if (result is NumericHealthResult numeric) Console.WriteLine($"Heart rate: {numeric.Value} bpm at {numeric.Start:T}");}Observing Blood Pressure
Section titled “Observing Blood Pressure”Blood pressure observations yield BloodPressureResult — use pattern matching:
await foreach (var result in health.Observe(DataType.BloodPressure, cancelToken: cts.Token)){ if (result is BloodPressureResult bp) Console.WriteLine($"BP: {bp.Systolic}/{bp.Diastolic} mmHg");}Custom Polling Interval (Android)
Section titled “Custom Polling Interval (Android)”On Android, observation uses polling. You can control the interval:
// Poll every 10 seconds instead of the default 5await foreach (var result in health.Observe( DataType.StepCount, pollingInterval: TimeSpan.FromSeconds(10), cancelToken: cts.Token)){ // ...}On iOS, the pollingInterval parameter is ignored — observation is push-based.
ViewModel Pattern
Section titled “ViewModel Pattern”public partial class HeartRateMonitorViewModel(IHealthService health) : ObservableObject{ CancellationTokenSource? cts;
[ObservableProperty] double currentHeartRate;
[ObservableProperty] bool isObserving;
[RelayCommand] async Task StartObservingAsync() { await health.RequestPermissions(DataType.HeartRate);
cts = new CancellationTokenSource(); IsObserving = true;
try { await foreach (var result in health.Observe(DataType.HeartRate, cancelToken: cts.Token)) { if (result is NumericHealthResult numeric) CurrentHeartRate = numeric.Value; } } catch (OperationCanceledException) { } finally { IsObserving = false; } }
[RelayCommand] void StopObserving() { cts?.Cancel(); cts?.Dispose(); cts = null; }}Cancellation
Section titled “Cancellation”Stop observing by cancelling the CancellationToken. This cleanly stops the underlying platform query (iOS) or polling loop (Android) and completes the async enumerable.
var cts = new CancellationTokenSource();
// Start observing in a background taskvar observeTask = Task.Run(async () =>{ await foreach (var result in health.Observe(DataType.StepCount, cancelToken: cts.Token)) { // process result }});
// Later, stop observingcts.Cancel();await observeTask;Best Practices
Section titled “Best Practices”- Always request permissions first — Call
RequestPermissionsbeforeObserve - Cancel when done — Always cancel the token when you no longer need updates to free platform resources
- Pattern match results —
Observereturns the baseHealthResulttype; cast toNumericHealthResultorBloodPressureResultas needed - Handle OperationCanceledException — This is expected when the token is cancelled
- UI thread — Results arrive on background threads; dispatch to the main thread for UI updates