Android Health Connect is an SDK-based provider. Your Android consumer app would embed the Vital Mobile SDKs on a supported stack. Data are then pulled from the Health Connect data store on the user’s Android device.

Refer to the Mobile SDK Installation and Vital Health SDK guides for SDK installation instructions and general API usage. This guide contains information on the the behaviour and required configuration specific to the Android Health Connect integration.

Configure your AndroidManifest.xml

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_HYDRATION

Foreground Service permissions

Vital Health SDK runs all its data sync workers inside a short-service Foreground Services.

At build time, Vital Health SDK injects all the required shortService Foreground Services declarations into your AndroidManifest.xml. You need not modify your app’s Manifest to enable this.

Vital Health SDK can run work inside the AndroidX WorkManager foregorund service, as well as our own SyncOnExactAlarmService. The merged AndroidManifest.xml of your app would include both of them.

Prepare your app architecture

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

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.

Synchronization

Sync On App Launch

When your app resumes from background (i.e., having no Activity), Vital Health SDK triggers a data sync on all the resources to which the user has granted read permission.

The SDK imposes a 2-minute throttle on automatic data sync. This is to prevent rapid app switching from causing an excessive amount of data sync work being scheduled.

Vital Health SDK relies on the AndroidX ProcessLifecycleOwner to get notified of your app’s resumption.

Running as Foreground Service

You cannot opt-out of this behaviour.

Vital Health SDK runs all its data sync workers inside a Foreground Service. Running as a foreground service helps in two ways:

  1. It ensures ongoing data sync can run to completion even if the user switches away from your app; and

  2. data sync workers have to be in foreground to read data from Health Connect. Health Connect prohibits reading data from background.

Android requires every Foreground Service to be associated with a user-visible notification. The OS typically grants a grace period of about 10 seconds before making the user aware of this notification. In other words, if the data sync worker completes within the grace period, no notification would be posted.

Vital Health SDK installs these notification copies by default:

ItemCopy
Notification TitleHealth Data Sync
Notification Content{APP_NAME} is synchronizing with Health Connect…
Channel TitleHealth Data Sync
Channel DescriptionNotifies when {APP_NAME} is synchronizing with Health Connect.

You can customize it through two ways:

Register your custom SyncNotificationBuilder through VitalHealthConnectManager.syncNotificationBuilder.

You should register it as soon as your app process is created. One way to ensure this is through the AndroidX Startup library. You can define an Initializer of your own that depends on the SDK VitalHealthConnectInitializer.

An example App Startup Initializer implementation

package com.example.app

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

class ExampleSyncNotificationBuilderInitializer: Initializer<Unit> {
    override fun create(context: Context) {
        val manager = VitalHealthConnectManager.getOrCreate(context)
        manager.syncNotificationBuilder = ExampleSyncNotificationBuilder
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> = mutableListOf(
        VitalHealthConnectInitializer::class.java,
    )
}

An example SyncNotificationBuilder implementation

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 ExampleSyncNotificationBuilder: SyncNotificationBuilder {
    override fun build(context: Context, resources: Set<VitalResource>): Notification {
        return NotificationCompat.Builder(context, createChannel(context))
            .setContentTitle("Example Sync")
            .setContentText("Syncing your data")
            .setOngoing(true)
            .setSmallIcon(android.R.drawable.ic_popup_sync)
            .build()
    }

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

Background Sync (Experimental)

This is an experimental feature which Vital is actively developing in collaboration with pilot customers. It may be changed or retracted at short notice.

Vital Health SDK supports an opt-in Android Background Sync feature. It provides a continuous monitoring experience with Android Health Connect, siimlar to Background Delivery for Apple HealthKit on iOS.

Sync Frequency

The Sync On App Launch behaviour is always-on.

When Background Sync is enabled, the SDK additionally schedules hourly sync using the Android Exact Alarms mechanism.

The OS has full discretion on whether to honour or defer the time as scheduled by the Vital Health SDK. This includes the policies of the base Android OS, as well as any vendor-specific OS augmentation.

For example, the device may enter Doze mode during device inactivity. Doze mode would batch and defer most Exact Alarms and other background work to run at a lower frequency.

Not to be confused with Alarm Clock apps, Exact Alarm is the technical name of an Android framework for scheduling app wake-ups in background at certain wall clock time. It is the Vital Health SDK being silently “alarmed” in background.

Your user will not be alarmed hourly as a result of enabling Background Sync.

However, if the data sync took longer than 10 seconds, the OS might nudge them of this occurrence with a user-visible notification. The notification content is configurable by you — see the “Running as Foreground Service” section for more details.

Runtime Permission Request

Since Android 12 (API Level 31), scheduling Exact Alarm (SCHEDULE_EXACT_ALARM) requires a runtime permission request from the user. More specifically:

  1. The user must go through an interactive flow to grant the “Alarms & Reminders” permission.
    • This flow is also reachable through the “Alarms & Reminders” section in Android Settings.
  2. The user may refuse to grant the “Alarms & Reminders” permission.
  3. The user may revoke the “Alarms & Reminders” permission through Android Settings at any time.

Since Android 13 (API Level 33), a new mutually exclusive USE_EXACT_ALARM permission was introduced. This permits your app to schedule Exact Alarms without interactive permission requests.

USE_EXACT_ALARM attracts scrutiny during Google Play Store review. Per the Google Play Exact alarm permission policy:

USE_EXACT_ALARM is a restricted permission and apps must only declare this permission if their core functionality supports the need for an exact alarm. Apps that request this restricted permission are subject to review, and those that do not meet the acceptable use case criteria will be disallowed from publishing on Google Play.

(excerpted on 20 March 2024)

If you choose to incorporate USE_EXACT_ALARM, you should prepare to justify to Google Play Store:

  1. Why hourly background sync is quintessential to your product experience; and
  2. Why the interactive permission request required by SCHEDULE_EXACT_ALARM is non-optimal to your product experience.

Here is a matrix summarizing the Exact Alarm permission request requirements:

Android OSAPI LevelRequirements
Android 11 or below<=30No interactive permission request.
Android 1231, 32SCHEDULE_EXACT_ALARM: Requires a runtime permission request. Revokable.
Android 13 and above>=33App must declare either:
  • SCHEDULE_EXACT_ALARM: Requires runtime permission request. Revokable.
  • USE_EXACT_ALARM: No interactive permission request.

Updating your AndroidManifest.xml

Your app’s AndroidManifest.xml must include the following uses-permission claims:

Make sure you read and understand Runtime User Permission before moving forward. This option is mutually exclusive with USE_EXACT_ALARM in the second tab.

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

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

</manifest>

Enabling Background Sync

You can enable Background Sync through enableBackgroundSyncContract() (an AndroidX ActivityResultContract).

If the runtime requires an interactive permission request, this contract will launch an Android Intent to request the “Alarms & Reminders” permission. The contract result is a boolean, indicating whether or not the Background Sync has been enabled successfully. For example, if the user did not grant the permission during the said Android Intent, the contract returns false.

Otherwise, the contract returns true synchronously when no user interaction is required.

You must launch this ActivityResultContract either through the in-built Android Compose support, or through the AndroidX Activity Result API if you do not use Android Compose.

You can also inspect if Background Sync has been enabled through the isBackgroundSyncEnabled property.

import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material3.*
import io.tryvital.vitalhealthconnect.VitalHealthConnectManager

val manager: VitalHealthConnectManager = ...

val permissionsLauncher = rememberLauncherForActivityResult(
    manager.enableBackgroundSyncContract()
) { success ->
    Log.i("VitalBackgroundSync", "Enabled? $success")

    val enabled = manager.isBackgroundSyncEnabled
    Log.i("VitalBackgroundSync", "Enabled? $enabled")
}

Button(onClick = { permissionsLauncher.launch(Unit) }) {
    Text("Enable Background Sync")
}

Disabling Background Sync

You can disable Background Sync through the disableBackgroundSync() method.

You can also inspect if Background Sync has been disabled through the isBackgroundSyncEnabled property.

import io.tryvital.vitalhealthconnect.VitalHealthConnectManager

val manager: VitalHealthConnectManager = ...
manager.disableBackgroundSync()

val enabled = manager.isBackgroundSyncEnabled
Log.i("VitalBackgroundSync", "Enabled? $enabled")

Miscellaneous

When you signOut() a user, Vital SDK will automatically disable Background Sync.