---
title: Enable Health Data Collection
---


## Overview

Sahha requires device data such as steps, sleep, heart rate, and other health signals in order to analyse health. This guide shows you how to configure and manage device sensors, request the right permissions, check collection status, and recover from incomplete setup.

The Sahha SDK acts as a bridge between your app and the device's health data sources. It simplifies permission handling, background collection, and the process of connecting Apple Health and Health Connect data to Sahha.

This page focuses on the sensor and permission part of the integration. Before requesting health access, make sure you have already completed project setup, SDK configuration, and user authentication.

---

## Before you request permissions

Only call `enableSensors()` after the following are complete:

- The SDK has been configured
- The correct user profile has been authenticated
- Your app has explained what access is being requested and why


### Project prerequisites

Make sure the platform setup work is already complete before you start requesting sensors:

- **iOS:** HealthKit capability, HealthKit Background Delivery, Background Modes, and usage descriptions must be configured in Xcode.
- **Android:** Matching `uses-permission` entries must be declared in `AndroidManifest.xml`, and devices running Android 9 through Android 13 need the Health Connect app installed.
- **Android native steps and sleep:** if your app relies on native steps or sleep synchronisation, include `WRITE_STEPS` and `WRITE_SLEEP` as well as the read permissions.

{% callout title="Complete setup first" type="warning" %}

If project capabilities, manifest permissions, or usage descriptions are missing, `enableSensors()` and `getSensorStatus()` may not behave as expected and data collection may fail.

Review the platform setup guides before continuing:

- [iOS Platform Setup](/docs/connect/sdk/installation/ios#set-up-xcode-project)
- [Android Platform Setup](/docs/connect/sdk/installation/android#set-up-android-project)

{% /callout %}

---

## Sensor Settings

You must specify which sensors the Sahha SDK should use. We recommend asking the user for permission to access only the sensors that your app actually needs. Requesting fewer, more relevant permissions usually improves opt-in rates and helps with App Store and Play Store review.

{% callout type="warning" title="Choose which sensors to request user permission for" %}

1. Specify an array of sensors to request permission for - **ONLY THOSE SENSORS will be requested.**
2. If you wish to add more sensors in future, append the new sensors to the array and follow the guide for prompting the user if sensors are not enabled.
3. **If you set the Sensor Settings value to an empty array `[]`, you will receive an ERROR.**

{% /callout %}

### SahhaSensor

Each `SahhaSensor` has a unique identifier which can be used with multiple SDK methods. A complete list is available below.

You can check the official documentation for the matching Android and iOS sensor to see platform-specific details.

**Some sensors are not available on all platforms.**

| SahhaSensor | Requires Wearable | Android Sensor | iOS Sensor |
| :--- | :--- | :--- | :--- |
| gender | No | _Sensor Not Available_ | [biologicalSex](https://developer.apple.com/documentation/healthkit/hkcharacteristictypeidentifier/1615283-biologicalsex) |
| date_of_birth | No | _Sensor Not Available_ | [dateOfBirth](https://developer.apple.com/documentation/healthkit/hkcharacteristictypeidentifier/1615780-dateofbirth) |
| sleep | No | [SleepSessionRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/SleepSessionRecord) | [sleepAnalysis](https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier/1615425-sleepanalysis) |
| steps | No | [StepsRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/StepsRecord) | [stepCount](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615548-stepcount) |
| floors_climbed | No | [FloorsClimbedRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/FloorsClimbedRecord) | [flightsClimbed](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615458-flightsclimbed) |
| heart_rate | Yes | [HeartRateRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/HeartRateRecord) | [heartRate](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615138-heartrate) |
| resting_heart_rate | Yes | [RestingHeartRateRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/RestingHeartRateRecord) | [restingHeartRate](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/2867756-restingheartrate) |
| walking_heart_rate_average | Yes | _Sensor Not Available_ | [walkingHeartRateAverage](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/2874170-walkingheartrateaverage) |
| heart_rate_variability_rmssd | Yes | [HeartRateVariabilityRmssdRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/HeartRateVariabilityRmssdRecord) | _Sensor Not Available_ |
| heart_rate_variability_sdnn | Yes | _Sensor Not Available_ | [heartRateVariabilitySDNN](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/2881127-heartratevariabilitysdnn) |
| blood_pressure_systolic | Yes | [BloodPressureRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BloodPressureRecord) | [bloodPressureSystolic](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615552-bloodpressuresystolic) |
| blood_pressure_diastolic | Yes | [BloodPressureRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BloodPressureRecord) | [bloodPressureDiastolic](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615233-bloodpressurediastolic) |
| blood_glucose | Yes | [BloodGlucoseRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BloodGlucoseRecord) | [bloodGlucose](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615455-bloodglucose) |
| vo2_max | Yes | [Vo2MaxRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/Vo2MaxRecord) | [vo2Max](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/2867757-vo2max) |
| oxygen_saturation | Yes | [OxygenSaturationRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/OxygenSaturationRecord) | [oxygenSaturation](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615377-oxygensaturation) |
| respiratory_rate | Yes | [RespiratoryRateRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/RespiratoryRateRecord) | [respiratoryRate](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615094-respiratoryrate) |
| active_energy_burned | No | [ActiveCaloriesBurnedRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/ActiveCaloriesBurnedRecord) | [activeEnergyBurned](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615771-activeenergyburned) |
| basal_energy_burned | No | _Sensor Not Available_ | [basalEnergyBurned](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615512-basalenergyburned) |
| total_energy_burned | No | [TotalCaloriesBurnedRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/TotalCaloriesBurnedRecord) | _Sensor Not Available_ |
| basal_metabolic_rate | No | [BasalMetabolicRateRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BasalMetabolicRateRecord) | _Sensor Not Available_ |
| time_in_daylight | No | _Sensor Not Available_ | [timeInDaylight](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/4168154-timeindaylight/) |
| body_temperature | No | [BodyTemperatureRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BodyTemperatureRecord) | [bodyTemperature](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615490-bodytemperature) |
| basal_body_temperature | Yes | [BasalBodyTemperatureRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BasalBodyTemperatureRecord) | [basalBodyTemperature](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615763-basalbodytemperature) |
| sleeping_wrist_temperature | Yes | _Sensor Not Available_ | [appleSleepingWristTemperature](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/3951065-applesleepingwristtemperature) |
| height | No | [HeightRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/HeightRecord) | [height](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615039-height) |
| weight | No | [WeightRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/WeightRecord) | [bodyMass](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615693-bodymass) |
| lean_body_mass | Yes | [LeanBodyMassRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/LeanBodyMassRecord) | [leanBodyMass](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615664-leanbodymass) |
| body_mass_index | No | _Sensor Not Available_ | [bodyMassIndex](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615698-bodymassindex) |
| body_fat | Yes | [BodyFatRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BodyFatRecord) | [bodyFatPercentage](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615666-bodyfatpercentage) |
| body_water_mass | No | [BodyWaterMassRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BodyWaterMassRecord) | _Sensor Not Available_ |
| bone_mass | No | [BoneMassRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/BoneMassRecord) | _Sensor Not Available_ |
| waist_circumference | No | _Sensor Not Available_ | [waistCircumference](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/2867758-waistcircumference) |
| stand_time | No | _Sensor Not Available_ | [appleStandTime](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/3174858-applestandtime) |
| move_time | No | _Sensor Not Available_ | [appleMoveTime](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/3131035-applemovetime) |
| exercise_time | No | _Sensor Not Available_ | [appleExerciseTime](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/1615696-appleexercisetime) |
| activity_summary | No | _Sensor Not Available_ | [activitySummary](https://developer.apple.com/documentation/healthkit/hkactivitysummary) |
| device_lock | No | [devicelock](https://developer.android.com/reference/android/devicelock/package-summary) | _Sensor Not Available_ |
| exercise | No | [ExerciseSessionRecord](https://developer.android.com/reference/androidx/health/connect/client/records/ExerciseSessionRecord) | [workout](https://developer.apple.com/documentation/healthkit/hkworkout) |
| energy_consumed | No | [NutritionRecord](https://developer.android.com/reference/kotlin/androidx/health/connect/client/records/NutritionRecord) | [dietaryEnergyConsumed](https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier/dietaryenergyconsumed) |

---

{% tabs %}

{% tab label="iOS" %}

```swift
public enum SahhaSensor: String, CaseIterable {
    case gender
    case date_of_birth
    case sleep
    case steps
    case floors_climbed
    case heart_rate
    case resting_heart_rate
    case walking_heart_rate_average
    case heart_rate_variability_sdnn
    case heart_rate_variability_rmssd
    case blood_pressure_systolic
    case blood_pressure_diastolic
    case blood_glucose
    case vo2_max
    case oxygen_saturation
    case respiratory_rate
    case active_energy_burned
    case basal_energy_burned
    case total_energy_burned
    case basal_metabolic_rate
    case time_in_daylight
    case body_temperature
    case basal_body_temperature
    case sleeping_wrist_temperature
    case height
    case weight
    case lean_body_mass
    case body_mass_index
    case body_fat
    case body_water_mass
    case bone_mass
    case waist_circumference
    case stand_time
    case move_time
    case exercise_time
    case activity_summary
    case device_lock
    case exercise
    case energy_consumed
}
```

{% /tab %}

{% tab label="Android" %}

```kotlin
enum class SahhaSensor {
    gender,
    date_of_birth,
    sleep,
    steps,
    floors_climbed,
    heart_rate,
    resting_heart_rate,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    walking_heart_rate_average,

    @Deprecated(
        message = "$IOS_ONLY. Please use heart_rate_variability_rmssd for Android instead",
        replaceWith = ReplaceWith("heart_rate_variability_rmssd"),
        level = DeprecationLevel.WARNING
    )
    heart_rate_variability_sdnn,
    heart_rate_variability_rmssd,
    blood_pressure_systolic,
    blood_pressure_diastolic,
    blood_glucose,
    vo2_max,
    oxygen_saturation,
    respiratory_rate,
    active_energy_burned,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    basal_energy_burned,
    total_energy_burned,
    basal_metabolic_rate,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    time_in_daylight,
    body_temperature,
    basal_body_temperature,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    sleeping_wrist_temperature,
    height,
    weight,
    lean_body_mass,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    body_mass_index,
    body_fat,
    body_water_mass,
    bone_mass,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    waist_circumference,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    stand_time,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    move_time,

    @Deprecated(
        message = IOS_ONLY,
        level = DeprecationLevel.WARNING
    )
    exercise_time,
    activity_summary,
    device_lock,
    exercise,
    energy_consumed
}
```

{% /tab %}

{% tab label="Flutter" %}

```dart
enum SahhaSensor {
  gender,
  date_of_birth,
  sleep,
  steps,
  floors_climbed,
  heart_rate,
  resting_heart_rate,
  walking_heart_rate_average,
  heart_rate_variability_sdnn,
  heart_rate_variability_rmssd,
  blood_pressure_systolic,
  blood_pressure_diastolic,
  blood_glucose,
  vo2_max,
  oxygen_saturation,
  respiratory_rate,
  active_energy_burned,
  basal_energy_burned,
  total_energy_burned,
  basal_metabolic_rate,
  time_in_daylight,
  body_temperature,
  basal_body_temperature,
  sleeping_wrist_temperature,
  height,
  weight,
  lean_body_mass,
  body_mass_index,
  body_fat,
  body_water_mass,
  bone_mass,
  waist_circumference,
  stand_time,
  move_time,
  exercise_time,
  activity_summary,
  device_lock,
  exercise,
  energy_consumed,
}
```

{% /tab %}

{% tab label="React Native" %}

```typescript
export enum SahhaSensor {
  gender = 'gender',
  date_of_birth = 'date_of_birth',
  sleep = 'sleep',
  steps = 'steps',
  floors_climbed = 'floors_climbed',
  heart_rate = 'heart_rate',
  resting_heart_rate = 'resting_heart_rate',
  walking_heart_rate_average = 'walking_heart_rate_average',
  heart_rate_variability_sdnn = 'heart_rate_variability_sdnn',
  heart_rate_variability_rmssd = 'heart_rate_variability_rmssd',
  blood_pressure_systolic = 'blood_pressure_systolic',
  blood_pressure_diastolic = 'blood_pressure_diastolic',
  blood_glucose = 'blood_glucose',
  vo2_max = 'vo2_max',
  oxygen_saturation = 'oxygen_saturation',
  respiratory_rate = 'respiratory_rate',
  active_energy_burned = 'active_energy_burned',
  basal_energy_burned = 'basal_energy_burned',
  total_energy_burned = 'total_energy_burned',
  basal_metabolic_rate = 'basal_metabolic_rate',
  time_in_daylight = 'time_in_daylight',
  body_temperature = 'body_temperature',
  basal_body_temperature = 'basal_body_temperature',
  sleeping_wrist_temperature = 'sleeping_wrist_temperature',
  height = 'height',
  weight = 'weight',
  lean_body_mass = 'lean_body_mass',
  body_mass_index = 'body_mass_index',
  body_fat = 'body_fat',
  body_water_mass = 'body_water_mass',
  bone_mass = 'bone_mass',
  waist_circumference = 'waist_circumference',
  stand_time = 'stand_time',
  move_time = 'move_time',
  exercise_time = 'exercise_time',
  activity_summary = 'activity_summary',
  device_lock = 'device_lock',
  exercise = 'exercise',
  energy_consumed = 'energy_consumed',
}
```

{% /tab %}

{% tab label="Ionic Capacitor" %}

```typescript
enum SahhaSensor {
  gender = 'gender',
  date_of_birth = 'date_of_birth',
  sleep = 'sleep',
  steps = 'steps',
  floors_climbed = 'floors_climbed',
  heart_rate = 'heart_rate',
  resting_heart_rate = 'resting_heart_rate',
  walking_heart_rate_average = 'walking_heart_rate_average',
  heart_rate_variability_sdnn = 'heart_rate_variability_sdnn',
  heart_rate_variability_rmssd = 'heart_rate_variability_rmssd',
  blood_pressure_systolic = 'blood_pressure_systolic',
  blood_pressure_diastolic = 'blood_pressure_diastolic',
  blood_glucose = 'blood_glucose',
  vo2_max = 'vo2_max',
  oxygen_saturation = 'oxygen_saturation',
  respiratory_rate = 'respiratory_rate',
  active_energy_burned = 'active_energy_burned',
  basal_energy_burned = 'basal_energy_burned',
  total_energy_burned = 'total_energy_burned',
  basal_metabolic_rate = 'basal_metabolic_rate',
  time_in_daylight = 'time_in_daylight',
  body_temperature = 'body_temperature',
  basal_body_temperature = 'basal_body_temperature',
  sleeping_wrist_temperature = 'sleeping_wrist_temperature',
  height = 'height',
  weight = 'weight',
  lean_body_mass = 'lean_body_mass',
  body_mass_index = 'body_mass_index',
  body_fat = 'body_fat',
  body_water_mass = 'body_water_mass',
  bone_mass = 'bone_mass',
  waist_circumference = 'waist_circumference',
  stand_time = 'stand_time',
  move_time = 'move_time',
  exercise_time = 'exercise_time',
  activity_summary = 'activity_summary',
  device_lock = 'device_lock',
  exercise = 'exercise',
  energy_consumed = 'energy_consumed',
}
```

{% /tab %}

{% /tabs %}

### (Android) Uses Permission - **IMPORTANT INFO**

{% callout type="warning" title="Specifying sensors for Android" %}

For every sensor you specify in `getSensorStatus()` or `enableSensors()`, you need matching `uses-permission` values in your `AndroidManifest.xml`.

[View the Android Platform Setup guide](/docs/connect/sdk/installation/android#set-up-android-project).

If the values do not match, you are likely to receive build errors and risk your app being rejected on the Google Play Store.

Also note:

- `SahhaSensor.device_lock` does **not** require any `AndroidManifest.xml` permission declarations.
- If you rely on native steps and sleep synchronisation, include `WRITE_STEPS` and `WRITE_SLEEP` in addition to the read permissions.

{% /callout %}

### Example: Android manifest for `steps` and `sleep`

```xml title=AndroidManifest.xml
<manifest ...>

    <!-- background access -->
    <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />

    <!-- Read permissions -->
    <uses-permission android:name="android.permission.health.READ_STEPS" />
    <uses-permission android:name="android.permission.health.READ_SLEEP" />

    <!-- Write permissions for native steps / sleep synchronisation -->
    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
    <uses-permission android:name="android.permission.health.WRITE_SLEEP" />

    <application ...>
        ...
    </application>
</manifest>
```

---

## Explain sensor access before calling `enableSensors()`

After `configure()` and `authenticate()` are complete, show a dedicated screen explaining:

- Which sensors are being requested
- Why they are needed for your use case
- How enabling them improves the app experience

This gives users the right context before the system permission prompts appear and usually leads to a better opt-in rate than showing the native permission UI without explanation.

### Example: enable sensors screen

![Enable sensors screen](/images/sensor-ux/enable-sensors.png)

### Example copy for your permission explainer screen

```text
Title
Enable health data collection

Body
We use your steps, sleep, and heart rate data to personalise your daily
health insights and show more accurate trends over time.

Why we ask
- Steps help us understand movement patterns
- Sleep helps us calculate recovery and routine
- Heart rate adds extra context for effort and readiness

Primary CTA
Enable Health Data

Secondary CTA
Not now
```

{% callout title="Only request what you can clearly justify" %}

Apple and Google are strict about sensitive permission use. Only request the sensors that directly support the features your app provides, and explain that value in plain language before calling `enableSensors()`.

{% /callout %}

---

## About the device sensor status

Sensors can have multiple possible statuses that indicate whether they are enabled, unavailable, or still pending.

`getSensorStatus()` is the best way to determine whether collection is fully active. In a healthy onboarding flow, you should use it:

- after the user finishes the permission prompt
- on later app launches
- after app updates that introduce new required sensors
- whenever you need to decide whether to show onboarding again or display a recovery banner

A sensor set should only be treated as fully ready when `getSensorStatus()` returns `enabled`.
In practice, that is the state where the required permissions are in place and the SDK collectors are running.

{% tabs %}

{% tab label="iOS" %}

```swift
public enum SensorStatus: Int {
    case pending = 0 // Sensors pending (before prompting user for permission)
    case unavailable = 1 // Sensors not supported by user's device
    case disabled = 2 // Sensors disabled (after prompting user for permission)
    case enabled = 3 // Sensors enabled (after prompting user for permission)
}
```

{% /tab %}

{% tab label="Android" %}

```kotlin
enum class SahhaSensorStatus {
    pending, // Sensors pending user permission
    unavailable, // Sensors not supported by the user's device
    disabled, // Sensors disabled by the user
    enabled // Sensors enabled by the user
}
```

{% /tab %}

{% tab label="Flutter" %}

```dart
enum SahhaSensorStatus {
  pending, // Sensors pending user permission
  unavailable, // Sensors not supported by the user's device
  disabled, // Sensors disabled by the user
  enabled, // Sensors enabled by the user
}
```

{% /tab %}

{% tab label="React Native" %}

```typescript
enum SahhaSensorStatus {
  pending = 0, // Sensors pending user permission
  unavailable = 1, // Sensors not supported by the user's device
  disabled = 2, // Sensors disabled by the user
  enabled = 3, // Sensors enabled by the user
}
```

{% /tab %}

{% tab label="Ionic Capacitor" %}

```typescript
enum SahhaSensorStatus {
  pending = 0, // Sensor data is pending user permission
  unavailable = 1, // Sensor data is not supported by the user's device
  disabled = 2, // Sensor data has been disabled by the user
  enabled = 3, // Sensor data has been enabled by the user
}
```

{% /tab %}

{% /tabs %}

### (iOS) Permission Privacy - **IMPORTANT INFO**

{% callout title="Apple limits the ability to detect the true sensor status to protect user privacy" type="warning" %}

Apple documentation:

`To help protect the user’s privacy, your app doesn’t know whether the user granted or denied permission to read data from HealthKit. If the user denied permission, attempts to query data from HealthKit return only samples that your app successfully saved to the HealthKit store.`

This means that if a sensor is available, the only possible `SensorStatus` values on iOS are usually:

- `pending` if you have not already prompted the user for permission
- `enabled` if you have already prompted the user for permission

The `disabled` status is not triggered even if the user declines permission.

The `disabled` status is only included in the iOS SDK to keep parity with the Android SDK.

{% /callout %}

---

## Getting the device sensor status

You can check the current status of a sensor set by calling `getSensorStatus()`. This method is asynchronous and returns the updated `SahhaSensorStatus` in its callback.

{% callout title="Configure the SDK before you get sensor status" type="warning" %}

On app launch, `SensorStatus` will always be `pending` until the SDK has been configured. You must call `configure()` before you can trust the result of `getSensorStatus()`.

We suggest calling `getSensorStatus()` after configuration is complete. In most production onboarding flows, you should also make sure the user is authenticated before using the result to decide the next step.

{% /callout %}

{% tabs %}

{% tab label="iOS" %}

```swift title=MyApp.swift
// Get status of `steps` and `sleep` sensors
Sahha.getSensorStatus([SahhaSensor.steps, SahhaSensor.sleep]) { error, sensorStatus in
    if let error = error {
        print(error)
    } else if sensorStatus == .pending {
        // Sensors are NOT enabled and ready - show your custom UI before asking for user permission
    } else if sensorStatus == .enabled {
        // Sensors are enabled and ready
    } else {
        // Sensors are disabled or unavailable
    }
}
```

{% /tab %}

{% tab label="Android" %}

```kotlin title=MainActivity.kt
// Get status of `steps` and `sleep` sensors
Sahha.getSensorStatus(
    this@MainActivity,
    setOf<SahhaSensor>(SahhaSensor.steps, SahhaSensor.sleep)
) { error, status ->
    if (error != null) {
        println(error)
    } else if (status == SahhaSensorStatus.pending) {
        // Sensors are NOT enabled and ready - show your custom UI before asking for user permission
    } else if (status == SahhaSensorStatus.enabled) {
        // Sensors are enabled and ready
    } else {
        // Sensors are disabled or unavailable
    }
}
```

{% /tab %}

{% tab label="Flutter" %}

```dart title=MyApp.dart
// Get status of `steps` and `sleep` sensors
SahhaFlutter.getSensorStatus(
  sensors: [SahhaSensor.steps, SahhaSensor.sleep],
).then((value) {
  debugPrint('Sensor status: ${value.name}');
  setState(() {
    sensorStatus = value;
  });

  if (sensorStatus == SahhaSensorStatus.pending) {
    // Sensors are NOT enabled and ready - show your custom UI before asking for user permission
  } else if (sensorStatus == SahhaSensorStatus.enabled) {
    // Sensors are enabled and ready
  } else {
    // Sensors are disabled or unavailable
  }
}).catchError((error, stackTrace) {
  debugPrint(error.toString());
});
```

{% /tab %}

{% tab label="React Native" %}

```typescript title=MyApp.tsx
// Get status of `steps` and `sleep` sensors
Sahha.getSensorStatus(
  [SahhaSensor.steps, SahhaSensor.sleep],
  (error: string, value: SahhaSensorStatus) => {
    if (error) {
      console.error(`Error: ${error}`);
    } else {
      console.log(`Sensor Status: ${value}`);
      setSensorStatus(value);

      if (value === SahhaSensorStatus.pending) {
        // Sensors are NOT enabled and ready - show your custom UI before asking for user permission
      } else if (value === SahhaSensorStatus.enabled) {
        // Sensors are enabled and ready
      } else {
        // Sensors are disabled or unavailable
      }
    }
  }
);
```

{% /tab %}

{% tab label="Ionic Capacitor" %}

```javascript title=MyApp.js
// Choose which sensors you want to check for your project
const sensors = [
  SahhaSensor.sleep,
  SahhaSensor.steps,
  SahhaSensor.floors_climbed,
  SahhaSensor.active_energy_burned,
  SahhaSensor.heart_rate,
];

Sahha.getSensorStatus({ sensors }).then(
  function (response) {
    console.log(response.status);
  },
  function (error) {
    console.log(error);
  }
);
```

{% /tab %}

{% /tabs %}

---

## Enabling device sensors

Before the SDK can start collecting data, you need to enable sensors by calling `enableSensors()`. This method is asynchronous and returns the updated `SahhaSensorStatus` in its callback.

### Use a button - **IMPORTANT INFO**

{% callout title="ALWAYS use a button to enable sensors" type="warning" %}

**DO NOT** call `enableSensors()` inside app init code or inside `useEffect` / auto-run lifecycle code.

`enableSensors()` displays system UI for the user. If you call it on init, your user may be shown a permission popup immediately on every app launch.

**This is poor user experience and may cause your app to be rejected by Apple or Google.**

Always use `enableSensors()` from a user action such as a button press on your signup screen, onboarding flow, or health-permission screen.

{% /callout %}

{% tabs %}

{% tab label="iOS" %}

```swift title=MyApp.swift
// ALWAYS use a button to enable sensors
Button {

    // Enable `steps` and `sleep` sensors
    Sahha.enableSensors([SahhaSensor.steps, SahhaSensor.sleep]) { error, sensorStatus in
        if let error = error {
            print(error)
        } else if sensorStatus == .enabled {
            // Sensors are enabled and ready
        } else {
            // Sensors are disabled or unavailable
        }
    }

} label: {
    HStack {
        Spacer()
        Text("Enable Sensors")
        Spacer()
    }
}
```

{% /tab %}

{% tab label="Android" %}

```kotlin title=MainActivity.kt
// ALWAYS use a button to enable sensors
Button(onClick = {

    // Enable `steps` and `sleep` sensors
    Sahha.enableSensors(
        this@MainActivity,
        setOf<SahhaSensor>(SahhaSensor.steps, SahhaSensor.sleep)
    ) { error, status ->
        if (error != null) {
            println(error)
        } else if (status == SahhaSensorStatus.enabled) {
            // Sensors are enabled and ready
        } else {
            // Sensors are disabled or unavailable
        }
    }

}) {
    Text("Enable Sensors")
}
```

{% /tab %}

{% tab label="Flutter" %}

```dart title=MyApp.dart
// ALWAYS use a button to enable sensors
ElevatedButton(
  style: ElevatedButton.styleFrom(
    minimumSize: const Size.fromHeight(40),
    padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
    textStyle: const TextStyle(fontSize: 16),
  ),
  onPressed: () {
    // Enable `steps` and `sleep` sensors
    SahhaFlutter.enableSensors(
      sensors: [SahhaSensor.steps, SahhaSensor.sleep],
    ).then((value) {
      debugPrint('Sensor status: ${value.name}');
      setState(() {
        sensorStatus = value;
      });

      if (sensorStatus == SahhaSensorStatus.enabled) {
        // Sensors are enabled and ready
      } else {
        // Sensors are disabled or unavailable
      }
    }).catchError((error, stackTrace) {
      debugPrint(error.toString());
    });
  },
  child: const Text('ENABLE SENSORS'),
)
```

{% /tab %}

{% tab label="React Native" %}

```typescript title=MyApp.tsx
// ALWAYS use a button to enable sensors
<Button
  title="ENABLE SENSORS"
  onPress={() => {
    // Enable `steps` and `sleep` sensors
    Sahha.enableSensors(
      [SahhaSensor.steps, SahhaSensor.sleep],
      (error: string, value: SahhaSensorStatus) => {
        if (error) {
          console.error(`Error: ${error}`);
        } else {
          console.log(`Sensor Status: ${value}`);
          if (value === SahhaSensorStatus.enabled) {
            // Sensors are enabled and ready
          } else {
            // Sensors are disabled or unavailable
          }
        }
      }
    );
  }}
/>
```

{% /tab %}

{% tab label="Ionic Capacitor" %}

```html title=MyApp.html
<div>
  <button onclick="enableSensors()">Enable Sensors</button>
</div>
```

```javascript title=MyApp.js
// ALWAYS use a button to enable sensors
window.enableSensors = () => {
  // Choose which sensors you want to enable for your project
  const sensors = [
    SahhaSensor.sleep,
    SahhaSensor.steps,
    SahhaSensor.floors_climbed,
    SahhaSensor.active_energy_burned,
    SahhaSensor.heart_rate,
  ];

  // Enable the sensors
  Sahha.enableSensors({ sensors }).then(
    function (response) {
      console.log(response.status);
    },
    function (error) {
      console.log(error);
    }
  );
};
```

{% /tab %}

{% /tabs %}

### (iOS) Sleep Sensor - **IMPORTANT INFO**

{% callout title="Set up Sleep before using the sleep sensor" type="warning" %}

For the Sahha SDK to collect data from the `sleep` sensor, Sleep functionality must already be enabled by your user **before** calling `enableSensors()`.

If the HealthKit permission flow has not been shown yet and `getSensorStatus()` is still `pending`, this is a good time to show custom UI that helps the user set up Sleep in the Health app before you request access.

Read more in Apple's setup guide:

[Sleep for iOS](https://support.apple.com/en-us/HT211685)

{% /callout %}

---

## Prompt the user if sensors are not enabled

Users do not always enable every permission during onboarding. Also, later app releases may introduce new sensors that require extra access.

If `getSensorStatus()` does **not** return `enabled`, show an in-app banner or prompt that helps the user return to sensor setup.

A persistent in-app prompt is often the easiest way to recover from:

- Incomplete onboarding
- Previously denied permissions
- Newly added sensors after an app update
- Platform-specific edge cases where expected data is still missing

### Example: enable sensors banner

![Enable sensors banner](/images/sensor-ux/sensor-banner.png)

### Example banner copy

```text
Title
Finish enabling health access

Body
We still need access to Steps and Sleep so we can keep your health insights up to date.

Primary action
Review permissions

Secondary action
Not now
```

### Example decision logic for a recovery banner

```text
status = getSensorStatus(requiredSensors)

if status != enabled:
  showBanner(
    title: "Finish enabling health access",
    action: "Review permissions"
  )
else:
  hideBanner()
```

---

## Offer a manual permissions recovery flow

If you want to go the extra mile, provide a fallback flow that helps users manually verify and re-enable permissions.

This is especially helpful in two situations:

- the user is still having trouble enabling sensors
- on iOS, where the system may restrict your ability to know the exact read-permission state after the first permission flow

In these cases, `getSensorStatus()` may say `enabled` even though expected data is no longer arriving.

A practical recovery flow looks like this:

1. Check `getSensorStatus()` for the sensors your app depends on.
2. If the status is `enabled` but expected data is still missing, make a lightweight verification request.
3. If that request still returns no expected result, show a recovery banner or help screen.
4. Give the user a button that calls `openAppSettings()`.

### Example: verify expected data after permissions look enabled

Use the method that matches the part of the product you are validating:

- `getStats()` if you want to verify raw sensor-driven daily stats such as steps or sleep
- `getBiomarkers()` if your product depends on Sahha-generated biomarkers

```text
status = getSensorStatus(requiredSensors)

if status == enabled:
  stats = getStats(steps or sleep for a period you expect data to exist)

  if stats are missing:
    showManualPermissionRecoveryHelp()
```

### Guide users to settings

Your help screen can guide users to:

- Open your app in system settings
- Review Apple Health or Health Connect access for your app
- Re-enable the specific sensors your app requires
- Return to the app and retry data collection

---

## Open App Settings

If a user disables a sensor, or you need them to manually re-check their permissions, send them to settings with `openAppSettings()`.

{% tabs %}

{% tab label="iOS" %}

```swift title=MyApp.swift
Sahha.openAppSettings()
```

{% /tab %}

{% tab label="Android" %}

```kotlin title=MainActivity.kt
Sahha.openAppSettings(this@MainActivity)
```

{% /tab %}

{% tab label="Flutter" %}

```dart title=MyApp.dart
SahhaFlutter.openAppSettings()
```

{% /tab %}

{% tab label="React Native" %}

```typescript title=MyApp.tsx
Sahha.openAppSettings();
```

{% /tab %}

{% tab label="Ionic Capacitor" %}

```javascript title=MyApp.js
Sahha.openAppSettings();
```

{% /tab %}

{% /tabs %}

### (iOS) Permission Changes - **IMPORTANT INFO**

{% callout title="Your app will terminate if iOS permissions change while it is backgrounded" type="warning" %}

If the user enables or disables a Health permission from device settings while your app is in the background, iOS will force your app to terminate. This is intentional system behaviour and the app will need to be relaunched.

{% /callout %}

---

## Platform caveats that affect real-world data collection

### Android: Health Connect availability

Health Connect is available on Android 9 (API 28) and above. On Android 9 through Android 13, the user must install the Health Connect app. On Android 14 and above, Health Connect is built in.

If a user cannot complete the permission flow on Android, check that Health Connect is available and installed before troubleshooting anything else.

### Historical readback limits

Both Apple Health and Health Connect can only provide a limited amount of historical data before the first successful permission grant. If the app is later uninstalled and reinstalled, that read window resets from the new permission date.

#### Example

```text
A user first grants permission on March 30, 2023.
The earliest readable data may be February 28, 2023 onward.

The user deletes the app on May 10, 2023.
They reinstall it and grant permission again on May 15, 2023.
The earliest readable data may now be April 15, 2023 onward.
```

This is important when validating onboarding because a fresh install may not immediately show older historical data you expected to see.

### iOS: locked-device restriction

Apple Health restricts reading data while the device is locked. Data recorded while the device is locked can become available after the user unlocks the phone again.

If you are testing overnight or background collection, remember that data availability may not update until after unlock.

---

## Why this flow matters

This flow helps ensure the SDK is set up correctly and actually delivering data.

- `configure()` connects the SDK to the correct environment
- `authenticate()` associates the SDK with the correct user
- `enableSensors()` requests the required health permissions
- `getSensorStatus()` confirms whether collection is ready
- If status is not `enabled`, banners help repair incomplete permission states
- If status looks enabled but expected data is still missing, offer a manual recovery flow and `openAppSettings()`

When these pieces are implemented together, you get a much more reliable onboarding experience and a better chance of continuous data collection.

---

This order helps ensure the SDK is connected to the correct environment, associated with the correct user profile, and ready to start collecting the sensor data your product needs.
