Android Health Connect is an SDK-based provider. This means that you access your user’s wearable data by embedding either our Native Android, Flutter or React Native SDK into your own Android app. We have a guide for each of these SDKs that you should follow for more detail.

Configure your App Manifest

Health Connect privacy dialogue

Missing manifest declarations may result in Health Connect ignoring your app.

Check the Health Connect Getting Started guide for the official requirements.

Here is a minimum declaration example:

<manifest ...>
    <application ...>
        <!-- BEGIN: Mandatory for Health Connect permission flow (Android 13 or below) -->
        <!-- Must either be your MainActivity, or a separate Activity; cannot be an activity-alias. -->
        <activity android:name=".MainActivity" ...>
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>
        <!-- END -->

        <!-- BEGIN: Mandatory for Health Connect permission flow (Android 14 or above) -->
        <!-- Can be an activity or an activity-alias. -->
        <activity-alias
            android:name="ViewPermissionUsageActivity"
            android:exported="true"
            android:targetActivity=".MainActivity"
            android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
            <intent-filter>
                <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
                <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
            </intent-filter>
        </activity-alias>
        <!-- END -->
    </application>

    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>
</manifest>

Health Connect permissions

Your app manifest (AndroidManifest.xml) must declare all the read or write permissions for all the data types you intend to sync.

For example, if you intend to sync Blood Pressure records and Blood Glucose reocrds, your app manifest must contain the following <uses-permission> declarations:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- ... other declarations -->

    <!-- BEGIN: Health Connect permissions -->
    <uses-permission android:name="android.permission.health.READ_BLOOD_GLUCOSE" />
    <uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
    <!-- END: Health Connect permissions -->
</manifest>
SDK VitalResource typeRead permissions required
Profileandroid.permission.health.READ_HEIGHT
Bodyandroid.permission.health.READ_BODY_FAT
android.permission.health.READ_WEIGHT
Workoutandroid.permission.health.READ_EXERCISE
android.permission.health.READ_HEART_RATE
android.permission.health.READ_RESPIRATORY_RATE
android.permission.health.READ_DISTANCE
android.permission.health.READ_ACTIVE_CALORIES_BURNED
android.permission.health.READ_ELEVATION_GAINED
android.permission.health.READ_POWER
android.permission.health.READ_SPEED
Activityandroid.permission.health.READ_ACTIVE_CALORIES_BURNED
android.permission.health.READ_BASAL_METABOLIC_RATE
android.permission.health.READ_TOTAL_CALORIES_BURNED
android.permission.health.READ_DISTANCE
android.permission.health.READ_STEPS
android.permission.health.READ_FLOORS_CLIMBED
android.permission.health.READ_DISTANCE
android.permission.health.READ_VO2_MAX
Sleepandroid.permission.health.READ_SLEEP
android.permission.health.READ_HEART_RATE
android.permission.health.READ_RESPIRATORY_RATE
android.permission.health.READ_HEART_RATE_VARIABILITY
android.permission.health.READ_OXYGEN_SATURATION
android.permission.health.READ_RESTING_HEART_RATE
Glucoseandroid.permission.health.READ_BLOOD_GLUCOSE
BloodPressureandroid.permission.health.READ_BLOOD_PRESSURE
HeartRateandroid.permission.health.READ_HEART_RATE
Stepsandroid.permission.health.READ_STEPS
ActiveEnergyBurnedandroid.permission.health.READ_ACTIVE_CALORIES_BURNED
BasalEnergyBurnedandroid.permission.health.READ_ACTIVE_CALORIES_BURNED
android.permission.health.READ_BASAL_METABOLIC_RATE
android.permission.health.READ_TOTAL_CALORIES_BURNED
Waterandroid.permission.health.READ_NUTRITION

Foreground Service permissions

If you intend to enable the “Ensure data sync can run to completion” feature as mentioned below, you must make the following Foreground Service declarations in your App Manifest:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application ...>
        <!-- BEGIN: Mandatory for running Health Connect as foreground service on Android 14+ -->
        <service
            android:name="androidx.work.impl.foreground.SystemForegroundService"
            android:foregroundServiceType="shortService"
            android:exported="false">
        </service>
        <!-- END: Mandatory for running Health Connect as foreground service on Android 14+ -->
    </application>
</manifest>

Prepare your app architecture

Incompliance to the guidelines below may result in Health Connect permission flow failing to launch on specific Android OS versions.

Native Android

When requesting permission using the ActivityResultContract created by Vital Health createPermissionRequestContract(), you must launch the contract using AndroidX Activity Result API.

Attempts to launch the contract manually via the legacy Activity.startActivityForResult API would result in an android.content.ActivityNotFoundException exception on Android 14 and later.

React Native

The MainActivity of your Android host app project must be augmented with either of the two options below:

  1. Subclassing VitalHealthReactActivity provided by the Vital Health SDK com.vitalhealthreactnative package, instead of the default ReactActivity.

  2. Overriding onRequestPermissionsResult(...) so as to manually propagate permission request results to the Vital Health SDK.

// MainActivity in your host app project
open class MainActivity: ReactActivity() {
  override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
  ) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    // Propagate permission flow results to Vital Health SDK.
    VitalHealthReactNativeModule.onRequestPermissionsResult(
      this.reactInstanceManager,
      requestCode,
      permissions,
      grantResults
    )
  }
}

The argumentation above is a workaround to React Native internals AndroidX-incompatible handling of permission request result callbacks, which breaks the AndroidX Activity Result API required by Android Health Connect.

Flutter

The MainActivity of your Android host app project must subclass FlutterFragmentActivity instead of FlutterActivity.

This is because FlutterFragmentActivity is AndroidX compatible and therefore supports the Activity Result API as required by Android Health Connect, whilst FlutterActivity is not.

Synchronization

Sync On App Launch

Because Android Health Connect disallowes any data access when the app is in background, your app must be regularly opened by the end user for data to be synchronized. Hence, the Vital SDK can only provide Sync On App Launch as the default automatic behaviour.

This Sync On App Launch behaviour occurs as part of your Vital Health SDK configuration call:

  • Native Android: VitalHealthConnectManager.configureHealthConnectClient
  • Flutter: vital_health.configure
  • React Native: VitalHealth.configure

Before the call returns control to your application, the SDK checks whether that the Vital Core SDK has been signed in with a Vital user. If it does, it would automatically start an asynchronous job to synchronize all VitalResources that are known to have permissions granted.

Ensure data sync can run to completion

This API is only accessible in Android native code, as it is not expressible through the restrictive React Native and Flutter interop.

Because Android Health Connect does not allow any data reads while the request is in the background, any on-going synchronization can be easily disrupted by the end user putting your app into background.

To mitigate this, the Vital Health SDK can run the data synchronization process as a Foreground Service, ensuring uninterrupted Health Connect read access until the synchronization ends (sans any intervention by the OS). You can opt-in to this mode by supplying a SyncNotificationBuilder when you configure the Vital Health SDK.

Note that the SyncNotificationBuilder is required for opt-in, since the Android OS requires every Foreground Service to be accompanied by a user-visible notification.

Code example

First, you can create a SyncNotificationBuilder class that returns a simple Syncing {things} message:

package io.tryvital.sample

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Context.NOTIFICATION_SERVICE
import androidx.core.app.NotificationCompat
import io.tryvital.vitalhealthconnect.SyncNotificationBuilder
import io.tryvital.vitalhealthconnect.model.VitalResource

object VitalSyncNotificationBuilder: SyncNotificationBuilder {
    override fun build(context: Context, resources: Set<VitalResource>): Notification {
        return NotificationCompat.Builder(context, createChannel(context))
            .setContentTitle("Vital Sync")
            .setTicker("Vital Sync")
            .setContentText("Syncing ${resources.map { it.toString() }.joinToString(", ")}")
            .setOngoing(true)
            .setSmallIcon(android.R.drawable.ic_popup_sync)
            .build()
    }

    fun createChannel(context: Context): String {
        val importance = NotificationManager.IMPORTANCE_MIN
        val mChannel = NotificationChannel("VitalSyncNotificationBuilder", "Vital Sync", importance)
        mChannel.description = "Notifies when Vital is syncing your data"
        val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
        return mChannel.id
    }
}

Then you can make use of the App Startup framework to consistently initialize the Vital Health SDK with your SyncNotificationBuilder during app startup:

package io.tryvital.sample

import android.content.Context
import androidx.startup.Initializer
import io.tryvital.client.VitalClient
import io.tryvital.vitalhealthconnect.VitalHealthConnectManager

class VitalSDKInitializer : Initializer<VitalHealthConnectManager> {
    override fun create(context: Context): VitalHealthConnectManager {
        return VitalHealthConnectManager.getOrCreate(context).apply {
            if (VitalClient.Status.SignedIn in VitalClient.status) {
                configureHealthConnectClient(
                    syncNotificationBuilder = VitalSyncNotificationBuilder
                )
            }
        }
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}