Skip to content

Commit 6ecfaf2

Browse files
shounbikkuan2011
andauthored
Add Health connect snippets (#866)
* add health connect folder * add health connect snippets * fix reviews * fix reviews * fix reviews * spotless error * review fix * review fix --------- Co-authored-by: Kat Kuan <843428+kkuan2011@users.noreply.github.com>
1 parent 908c46d commit 6ecfaf2

31 files changed

Lines changed: 993 additions & 0 deletions

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ google-maps = "20.0.0"
6262
gradle-versions = "0.53.0"
6363
guava = "33.5.0-jre"
6464
guava-android = "33.5.0-jre"
65+
health-connect = "1.1.0-alpha11"
6566
hilt = "2.59.2"
6667
horologist = "0.8.3-alpha"
6768
junit = "4.13.2"
@@ -183,6 +184,7 @@ androidx-glance-appwidget-testing = { module = "androidx.glance:glance-appwidget
183184
androidx-glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "androidx-glance-appwidget" }
184185
androidx-glance-testing = { module = "androidx.glance:glance-testing", version.ref = "androidx-glance-appwidget" }
185186
androidx-graphics-shapes = "androidx.graphics:graphics-shapes:1.1.0"
187+
androidx-health-connect = { group = "androidx.health.connect", name = "connect-client", version.ref = "health-connect" }
186188
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
187189
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-compose" }
188190
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-runtime-compose" }

healthconnect/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

healthconnect/build.gradle.kts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.compose.compiler)
4+
}
5+
6+
android {
7+
namespace = "com.example.healthconnect"
8+
compileSdk = 37
9+
10+
defaultConfig {
11+
applicationId = "com.example.healthconnect"
12+
minSdk = 26
13+
targetSdk = 37
14+
versionCode = 1
15+
versionName = "1.0"
16+
17+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
}
19+
20+
buildTypes {
21+
release {
22+
isMinifyEnabled = false
23+
proguardFiles(
24+
getDefaultProguardFile("proguard-android-optimize.txt"),
25+
"proguard-rules.pro"
26+
)
27+
}
28+
}
29+
compileOptions {
30+
sourceCompatibility = JavaVersion.VERSION_11
31+
targetCompatibility = JavaVersion.VERSION_11
32+
}
33+
buildFeatures {
34+
compose = true
35+
}
36+
}
37+
38+
dependencies {
39+
implementation(libs.androidx.core.ktx)
40+
implementation(libs.androidx.lifecycle.runtime)
41+
implementation(libs.androidx.activity.compose)
42+
implementation(platform(libs.androidx.compose.bom))
43+
implementation(libs.androidx.compose.ui)
44+
implementation(libs.androidx.compose.ui.graphics)
45+
implementation(libs.androidx.compose.ui.tooling.preview)
46+
implementation(libs.androidx.compose.material3)
47+
implementation(libs.androidx.health.connect)
48+
implementation(libs.androidx.work.runtime.ktx)
49+
50+
debugImplementation(libs.androidx.compose.ui.tooling)
51+
debugImplementation(libs.androidx.compose.ui.test.manifest)
52+
}

healthconnect/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2026 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
https://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
18+
19+
<queries>
20+
<package android:name="com.google.android.apps.healthdata" />
21+
</queries>
22+
23+
<application
24+
android:allowBackup="true"
25+
android:icon="@mipmap/ic_launcher"
26+
android:label="@string/app_name"
27+
android:roundIcon="@mipmap/ic_launcher_round"
28+
android:supportsRtl="true"
29+
android:theme="@style/Theme.Snippets">
30+
31+
<activity
32+
android:name=".HealthConnectActivity"
33+
android:exported="true">
34+
<intent-filter>
35+
<action android:name="android.intent.action.MAIN" />
36+
<category android:name="android.intent.category.LAUNCHER" />
37+
</intent-filter>
38+
39+
<intent-filter>
40+
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
41+
</intent-filter>
42+
</activity>
43+
44+
</application>
45+
</manifest>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.healthconnect
18+
19+
import androidx.health.connect.client.HealthConnectClient
20+
import androidx.health.connect.client.changes.DeletionChange
21+
import androidx.health.connect.client.changes.UpsertionChange
22+
import androidx.health.connect.client.records.WeightRecord
23+
import androidx.health.connect.client.request.ChangesTokenRequest
24+
25+
class ChangesManager(private val healthConnectClient: HealthConnectClient) {
26+
27+
suspend fun getChangesToken(): String {
28+
// [START android_healthconnect_get_changes_token]
29+
val changesToken = healthConnectClient.getChangesToken(
30+
ChangesTokenRequest(recordTypes = setOf(WeightRecord::class))
31+
)
32+
// [END android_healthconnect_get_changes_token]
33+
return changesToken
34+
}
35+
36+
// [START android_healthconnect_process_changes]
37+
suspend fun processChanges(token: String): String {
38+
var nextChangesToken = token
39+
do {
40+
val response = healthConnectClient.getChanges(nextChangesToken)
41+
response.changes.forEach { change ->
42+
when (change) {
43+
is UpsertionChange -> { /* Process Upsert */ }
44+
is DeletionChange -> { /* Process Deletion */ }
45+
}
46+
}
47+
nextChangesToken = response.nextChangesToken
48+
} while (response.hasMore)
49+
return nextChangesToken
50+
}
51+
// [END android_healthconnect_process_changes]
52+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.healthconnect
18+
19+
import android.os.Bundle
20+
import androidx.activity.ComponentActivity
21+
import androidx.activity.compose.setContent
22+
import androidx.activity.enableEdgeToEdge
23+
import androidx.compose.foundation.layout.Box
24+
import androidx.compose.foundation.layout.fillMaxSize
25+
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.foundation.lazy.LazyColumn
27+
import androidx.compose.material3.Button
28+
import androidx.compose.material3.ExperimentalMaterial3Api
29+
import androidx.compose.material3.MaterialTheme
30+
import androidx.compose.material3.Scaffold
31+
import androidx.compose.material3.SnackbarHost
32+
import androidx.compose.material3.SnackbarHostState
33+
import androidx.compose.material3.Text
34+
import androidx.compose.material3.TopAppBar
35+
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.remember
37+
import androidx.compose.runtime.rememberCoroutineScope
38+
import androidx.compose.ui.Alignment
39+
import androidx.compose.ui.Modifier
40+
import androidx.compose.ui.platform.LocalContext
41+
import androidx.compose.ui.text.style.TextAlign
42+
import androidx.compose.ui.unit.dp
43+
import androidx.health.connect.client.HealthConnectClient
44+
import com.example.healthconnect.ui.theme.SnippetsTheme
45+
import kotlinx.coroutines.launch
46+
import java.time.Clock
47+
import java.time.Instant
48+
import java.time.Duration
49+
50+
class HealthConnectActivity : ComponentActivity() {
51+
override fun onCreate(savedInstanceState: Bundle?) {
52+
super.onCreate(savedInstanceState)
53+
enableEdgeToEdge()
54+
55+
setContent {
56+
SnippetsTheme {
57+
HealthConnectScreen(Modifier.fillMaxSize())
58+
}
59+
}
60+
}
61+
}
62+
63+
@OptIn(ExperimentalMaterial3Api::class)
64+
@Composable
65+
fun HealthConnectScreen(modifier: Modifier) {
66+
val context = LocalContext.current
67+
val coroutineScope = rememberCoroutineScope()
68+
val snackbarHostState = remember { SnackbarHostState() }
69+
70+
// [START android_healthconnect_get_client]
71+
val availabilityStatus = HealthConnectClient.getSdkStatus(context)
72+
if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
73+
Box(modifier = modifier.padding(16.dp), contentAlignment = Alignment.Center) {
74+
Text(
75+
text = "Health Connect is not available on this device. Please ensure it is installed and updated.",
76+
style = MaterialTheme.typography.bodyLarge,
77+
textAlign = TextAlign.Center
78+
)
79+
}
80+
return
81+
}
82+
83+
val healthConnectClient = remember {
84+
if (availabilityStatus == HealthConnectClient.SDK_AVAILABLE) {
85+
HealthConnectClient.getOrCreate(context)
86+
} else {
87+
null
88+
}
89+
}
90+
// [END android_healthconnect_get_client]
91+
92+
// Initialize our snippet manager
93+
val manager = remember(healthConnectClient) {
94+
healthConnectClient?.let { HealthConnectManager(it) }
95+
}
96+
97+
Scaffold(
98+
modifier = modifier,
99+
snackbarHost = { SnackbarHost(snackbarHostState) },
100+
topBar = { TopAppBar(title = { Text("Health Connect Snippets") }) },
101+
) { innerPadding ->
102+
LazyColumn(Modifier.padding(innerPadding).padding(horizontal = 16.dp)) {
103+
item {
104+
Text(
105+
text = if (healthConnectClient != null)
106+
"Health Connect is available"
107+
else
108+
"Health Connect is not available",
109+
modifier = Modifier.padding(vertical = 16.dp)
110+
)
111+
}
112+
113+
if (manager != null) {
114+
item {
115+
Button(onClick = {
116+
coroutineScope.launch {
117+
val startTime = Instant.now().minusSeconds(3600)
118+
val endTime = Instant.now()
119+
manager.insertSteps(startTime, endTime)
120+
snackbarHostState.showSnackbar("Steps inserted!")
121+
}
122+
}) {
123+
Text("Run: Insert Steps")
124+
}
125+
}
126+
127+
item {
128+
Button(
129+
modifier = Modifier.padding(top = 8.dp),
130+
onClick = {
131+
coroutineScope.launch {
132+
val startTime = Instant.now().minus(Duration.ofDays(1))
133+
val endTime = Instant.now()
134+
135+
val total = manager.readStepsAggregate(startTime, endTime)
136+
snackbarHostState.showSnackbar("Total Steps: $total")
137+
}
138+
}) {
139+
Text("Run: Read Steps Aggregate")
140+
}
141+
}
142+
}
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)