### Manage Exercise Sessions with ExerciseClient Source: https://context7.com/android/health-samples/llms.txt Manages exercise sessions including starting, pausing, resuming, and ending. It also includes preparing sensors and marking laps. Ensure HealthServicesClient is injected. ```kotlin import androidx.health.services.client.ExerciseClient import androidx.health.services.client.HealthServicesClient import androidx.health.services.client.data.* @Singleton class ExerciseClientManager @Inject constructor( healthServicesClient: HealthServicesClient ) { val exerciseClient: ExerciseClient = healthServicesClient.exerciseClient // Check exercise capabilities suspend fun getExerciseCapabilities(): ExerciseTypeCapabilities? { val capabilities = exerciseClient.getCapabilities() return if (ExerciseType.RUNNING in capabilities.supportedExerciseTypes) { capabilities.getExerciseTypeCapabilities(ExerciseType.RUNNING) } else null } // Prepare sensors before starting suspend fun prepareExercise() { val warmUpConfig = WarmUpConfig( exerciseType = ExerciseType.RUNNING, dataTypes = setOf(DataType.HEART_RATE_BPM, DataType.LOCATION) ) exerciseClient.prepareExercise(warmUpConfig) } // Start exercise with goals suspend fun startExercise() { val capabilities = getExerciseCapabilities() ?: return val dataTypes = setOf( DataType.HEART_RATE_BPM, DataType.HEART_RATE_BPM_STATS, DataType.CALORIES_TOTAL, DataType.DISTANCE_TOTAL ).intersect(capabilities.supportedDataTypes) val exerciseGoals = mutableListOf>() exerciseGoals.add( ExerciseGoal.createOneTimeGoal( DataTypeCondition( dataType = DataType.CALORIES_TOTAL, threshold = 250.0, comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL ) ) ) val config = ExerciseConfig( exerciseType = ExerciseType.RUNNING, dataTypes = dataTypes, isAutoPauseAndResumeEnabled = capabilities.supportsAutoPauseAndResume, isGpsEnabled = true, exerciseGoals = exerciseGoals ) exerciseClient.startExercise(config) } suspend fun pauseExercise() = exerciseClient.pauseExercise() suspend fun resumeExercise() = exerciseClient.resumeExercise() suspend fun endExercise() = exerciseClient.endExercise() suspend fun markLap() { if (exerciseClient.isExerciseInProgress()) { exerciseClient.markLap() } } } ``` -------------------------------- ### Real-time Heart Rate Measurement Source: https://context7.com/android/health-samples/llms.txt Use this snippet to get rapid heart rate measurements in the foreground. It requires registering a MeasureCallback and handles data and availability updates. ```kotlin import androidx.health.services.client.HealthServices import androidx.health.services.client.MeasureCallback import androidx.health.services.client.data.* class HealthServicesRepository(context: Context) { private val healthServicesClient = HealthServices.getClient(context) private val measureClient = healthServicesClient.measureClient suspend fun hasHeartRateCapability(): Boolean { val capabilities = measureClient.getCapabilitiesAsync().await() return DataType.HEART_RATE_BPM in capabilities.supportedDataTypesMeasure } fun heartRateMeasureFlow() = callbackFlow { val callback = object : MeasureCallback { override fun onAvailabilityChanged( dataType: DeltaDataType<*, *>, availability: Availability ) { if (availability is DataTypeAvailability) { trySendBlocking(MeasureMessage.MeasureAvailability(availability)) } } override fun onDataReceived(data: DataPointContainer) { val heartRateBpm = data.getData(DataType.HEART_RATE_BPM) trySendBlocking(MeasureMessage.MeasureData(heartRateBpm)) } } measureClient.registerMeasureCallback(DataType.HEART_RATE_BPM, callback) awaitClose { runBlocking { measureClient.unregisterMeasureCallbackAsync(DataType.HEART_RATE_BPM, callback).await() } } } } sealed class MeasureMessage { class MeasureAvailability(val availability: DataTypeAvailability) : MeasureMessage() class MeasureData(val data: List>) : MeasureMessage() } // Usage: healthServicesRepository.heartRateMeasureFlow().collect { when (message) { is MeasureMessage.MeasureData -> { message.data.forEach { println("Heart rate: ${it.value} bpm") } } is MeasureMessage.MeasureAvailability -> { println("Sensor availability: ${message.availability}") } } } ``` -------------------------------- ### Generate Sleep Session Data with Stages Source: https://context7.com/android/health-samples/llms.txt Creates and inserts a sleep session record into Health Connect, including detailed sleep stages (awake, light, deep, REM) with their respective start and end times. This function uses random durations for stages to simulate a sleep session. ```kotlin import androidx.health.connect.client.records.SleepSessionRecord // Generate sleep data with stages suspend fun generateSleepData() { val wakeUp = ZonedDateTime.now().withHour(7).withMinute(30) val bedtime = wakeUp.minusDays(1).withHour(22).withMinute(0) val stages = mutableListOf() var stageStart = bedtime while (stageStart < wakeUp) { val stageEnd = stageStart.plusMinutes(Random.nextLong(30, 120)) val checkedEnd = if (stageEnd > wakeUp) wakeUp else stageEnd stages.add( SleepSessionRecord.Stage( stage = listOf( SleepSessionRecord.STAGE_TYPE_AWAKE, SleepSessionRecord.STAGE_TYPE_DEEP, SleepSessionRecord.STAGE_TYPE_LIGHT, SleepSessionRecord.STAGE_TYPE_REM ).random(), startTime = stageStart.toInstant(), endTime = checkedEnd.toInstant() ) ) stageStart = checkedEnd } healthConnectClient.insertRecords(listOf( SleepSessionRecord( metadata = Metadata.manualEntry(), notes = "Good night's sleep", startTime = bedtime.toInstant(), startZoneOffset = bedtime.offset, endTime = wakeUp.toInstant(), endZoneOffset = wakeUp.offset, stages = stages ) )) ``` -------------------------------- ### Check Health Connect SDK Availability Source: https://context7.com/android/health-samples/llms.txt Verifies if the Health Connect SDK is available on the device. Use this before attempting to interact with the Health Connect API to ensure compatibility and proper setup. ```kotlin import androidx.health.connect.client.HealthConnectClient import android.os.Build const val MIN_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1 class HealthConnectManager(private val context: Context) { private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) } var availability = mutableStateOf(HealthConnectClient.SDK_UNAVAILABLE) private set fun checkAvailability() { availability.value = HealthConnectClient.getSdkStatus(context) } // Usage init { checkAvailability() when (availability.value) { HealthConnectClient.SDK_AVAILABLE -> { /* Ready to use */ } HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED -> { /* Needs update */ } HealthConnectClient.SDK_UNAVAILABLE -> { /* Not supported on this device */ } } } } ``` -------------------------------- ### Receive Real-time Exercise Updates Source: https://context7.com/android/health-samples/llms.txt Sets up a callback flow to receive real-time exercise updates, including metrics, lap summaries, and location availability. Ensure ExerciseClient is available. ```kotlin import androidx.health.services.client.ExerciseUpdateCallback import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.callbackFlow val exerciseUpdateFlow = callbackFlow { val callback = object : ExerciseUpdateCallback { override fun onExerciseUpdateReceived(update: ExerciseUpdate) { trySendBlocking(ExerciseMessage.ExerciseUpdateMessage(update)) } override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) { trySendBlocking(ExerciseMessage.LapSummaryMessage(lapSummary)) } override fun onRegistered() {} override fun onRegistrationFailed(throwable: Throwable) { close(throwable) } override fun onAvailabilityChanged( dataType: DataType<*, *>, availability: Availability ) { if (availability is LocationAvailability) { trySendBlocking(ExerciseMessage.LocationAvailabilityMessage(availability)) } } } exerciseClient.setUpdateCallback(callback) awaitClose { exerciseClient.clearUpdateCallbackAsync(callback) } } sealed class ExerciseMessage { class ExerciseUpdateMessage(val exerciseUpdate: ExerciseUpdate) : ExerciseMessage() class LapSummaryMessage(val lapSummary: ExerciseLapSummary) : ExerciseMessage() class LocationAvailabilityMessage(val locationAvailability: LocationAvailability) : ExerciseMessage() } // Usage in ViewModel: viewModelScope.launch { exerciseUpdateFlow.collect { message -> when (message) { is ExerciseMessage.ExerciseUpdateMessage -> { val metrics = message.exerciseUpdate.latestMetrics val state = message.exerciseUpdate.exerciseStateInfo.state // Update UI with current state and metrics } is ExerciseMessage.LapSummaryMessage -> { val lapCount = message.lapSummary.lapCount } is ExerciseMessage.LocationAvailabilityMessage -> { val gpsAvailable = message.locationAvailability } } } } ``` -------------------------------- ### Write Exercise Session with Related Data in Kotlin Source: https://context7.com/android/health-samples/llms.txt Creates a complete exercise session including steps, distance, calories, and heart rate data. All related records are inserted together as a single transaction. Ensure Health Connect client is initialized before use. ```kotlin import androidx.health.connect.client.records.* import androidx.health.connect.client.records.metadata.Metadata import androidx.health.connect.client.units.Energy import androidx.health.connect.client.units.Length import java.time.ZonedDateTime import kotlin.random.Random suspend fun writeExerciseSession( start: ZonedDateTime, end: ZonedDateTime ): InsertRecordsResponse { // Build heart rate series val heartRateSamples = mutableListOf() var time = start while (time.isBefore(end)) { heartRateSamples.add( HeartRateRecord.Sample( time = time.toInstant(), beatsPerMinute = (80 + Random.nextInt(80)).toLong() ) ) time = time.plusSeconds(30) } return healthConnectClient.insertRecords( listOf( ExerciseSessionRecord( metadata = Metadata.manualEntry(), startTime = start.toInstant(), startZoneOffset = start.offset, endTime = end.toInstant(), endZoneOffset = end.offset, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING, title = "Morning Run" ), StepsRecord( metadata = Metadata.manualEntry(), startTime = start.toInstant(), startZoneOffset = start.offset, endTime = end.toInstant(), endZoneOffset = end.offset, count = 5000L ), DistanceRecord( metadata = Metadata.manualEntry(), startTime = start.toInstant(), startZoneOffset = start.offset, endTime = end.toInstant(), endZoneOffset = end.offset, distance = Length.meters(4500.0) ), TotalCaloriesBurnedRecord( metadata = Metadata.manualEntry(), startTime = start.toInstant(), startZoneOffset = start.offset, endTime = end.toInstant(), endZoneOffset = end.offset, energy = Energy.calories(350.0) ), HeartRateRecord( metadata = Metadata.manualEntry(), startTime = start.toInstant(), startZoneOffset = start.offset, endTime = end.toInstant(), endZoneOffset = end.offset, samples = heartRateSamples ) ) ) } // Usage: val response = writeExerciseSession( start = ZonedDateTime.now().minusHours(1), end = ZonedDateTime.now() ) println("Inserted ${response.recordIdsList.size} records") ``` -------------------------------- ### Write, Read, Aggregate, and Delete Weight Records Source: https://context7.com/android/health-samples/llms.txt Demonstrates writing individual weight records, reading historical data within a time range, computing weekly averages, and deleting specific records. Ensure necessary permissions are granted before performing these operations. ```kotlin import androidx.health.connect.client.records.WeightRecord import androidx.health.connect.client.units.Mass // Write weight input suspend fun writeWeightInput(weight: WeightRecord) { healthConnectClient.insertRecords(listOf(weight)) } ``` ```kotlin import androidx.health.connect.client.records.WeightRecord import androidx.health.connect.client.time.TimeRangeFilter import androidx.health.connect.client.request.ReadRecordsRequest import java.time.Instant // Read weight history suspend fun readWeightInputs(start: Instant, end: Instant): List { val request = ReadRecordsRequest( recordType = WeightRecord::class, timeRangeFilter = TimeRangeFilter.between(start, end) ) return healthConnectClient.readRecords(request).records } ``` ```kotlin import androidx.health.connect.client.records.WeightRecord import androidx.health.connect.client.time.TimeRangeFilter import androidx.health.connect.client.request.AggregateRequest import androidx.health.connect.client.units.Mass import java.time.Instant // Compute weekly average suspend fun computeWeeklyAverage(start: Instant, end: Instant): Mass? { val request = AggregateRequest( metrics = setOf(WeightRecord.WEIGHT_AVG), timeRangeFilter = TimeRangeFilter.between(start, end) ) return healthConnectClient.aggregate(request)[WeightRecord.WEIGHT_AVG] } ``` ```kotlin import androidx.health.connect.client.records.WeightRecord // Delete weight record suspend fun deleteWeightInput(uid: String) { healthConnectClient.deleteRecords( WeightRecord::class, recordIdsList = listOf(uid), clientRecordIdsList = emptyList() ) } ``` ```kotlin import androidx.health.connect.client.records.Metadata import androidx.health.connect.client.records.WeightRecord import androidx.health.connect.client.units.Mass import java.time.Instant import java.time.ZoneOffset // Usage: writeWeightInput( WeightRecord( metadata = Metadata.manualEntry(), weight = Mass.kilograms(75.5), time = Instant.now(), zoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now()) ) ) val avgWeight = computeWeeklyAverage( Instant.now().minus(Duration.ofDays(7)), Instant.now() ) println("Weekly average: ${avgWeight?.inKilograms} kg") ``` -------------------------------- ### Define Daily Step and Floor Goals Source: https://context7.com/android/health-samples/llms.txt Defines passive goals for daily steps and floors climbed. Ensure the Health Services API is available and configured. ```kotlin import androidx.health.services.client.data.* // Define goals val dailyStepsGoal = PassiveGoal( DataTypeCondition( dataType = DataType.STEPS_DAILY, threshold = 10000, comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL ) ) val floorsGoal = PassiveGoal( DataTypeCondition( dataType = DataType.FLOORS_DAILY, threshold = 10, comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL ) ) ``` -------------------------------- ### Read Aggregated Session Data in Kotlin Source: https://context7.com/android/health-samples/llms.txt Reads aggregated metrics for an exercise session including total steps, distance, calories, and heart rate statistics (min, max, average). Requires a valid session UID and an initialized Health Connect client. ```kotlin import androidx.health.connect.client.request.AggregateRequest suspend fun readAssociatedSessionData(uid: String): ExerciseSessionData { val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid) val timeRangeFilter = TimeRangeFilter.between( startTime = exerciseSession.record.startTime, endTime = exerciseSession.record.endTime ) val aggregateRequest = AggregateRequest( metrics = setOf( ExerciseSessionRecord.EXERCISE_DURATION_TOTAL, StepsRecord.COUNT_TOTAL, DistanceRecord.DISTANCE_TOTAL, TotalCaloriesBurnedRecord.ENERGY_TOTAL, HeartRateRecord.BPM_AVG, HeartRateRecord.BPM_MAX, HeartRateRecord.BPM_MIN ), timeRangeFilter = timeRangeFilter, dataOriginFilter = setOf(exerciseSession.record.metadata.dataOrigin) ) val aggregateData = healthConnectClient.aggregate(aggregateRequest) return ExerciseSessionData( uid = uid, totalActiveTime = aggregateData[ExerciseSessionRecord.EXERCISE_DURATION_TOTAL], totalSteps = aggregateData[StepsRecord.COUNT_TOTAL], totalDistance = aggregateData[DistanceRecord.DISTANCE_TOTAL], totalEnergyBurned = aggregateData[TotalCaloriesBurnedRecord.ENERGY_TOTAL], minHeartRate = aggregateData[HeartRateRecord.BPM_MIN], maxHeartRate = aggregateData[HeartRateRecord.BPM_MAX], avgHeartRate = aggregateData[HeartRateRecord.BPM_AVG] ) } // Usage: val sessionData = readAssociatedSessionData(sessionUid) println("Steps: ${sessionData.totalSteps}") println("Distance: ${sessionData.totalDistance?.inMeters} meters") println("Heart Rate: ${sessionData.avgHeartRate} bpm (avg)") ``` -------------------------------- ### Manage Health Connect Permissions Source: https://context7.com/android/health-samples/llms.txt Handles Health Connect permissions by checking granted permissions and requesting missing ones. Use `requestPermissionsActivityContract` to launch the permission request flow. ```kotlin import androidx.health.connect.client.PermissionController import androidx.activity.result.contract.ActivityResultContract class HealthConnectManager(private val context: Context) { private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) } // Check if all required permissions are granted suspend fun hasAllPermissions(permissions: Set): Boolean { return healthConnectClient.permissionController.getGrantedPermissions() .containsAll(permissions) } // Get the activity result contract for requesting permissions fun requestPermissionsActivityContract(): ActivityResultContract, Set> { return PermissionController.createRequestPermissionResultContract() } // Revoke all permissions (useful for logout/reset) suspend fun revokeAllPermissions() { healthConnectClient.permissionController.revokeAllPermissions() } } // Usage in Activity/Composable: val permissions = setOf( HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getWritePermission(ExerciseSessionRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class) ) val permissionsLauncher = registerForActivityResult( manager.requestPermissionsActivityContract() ) { granted -> if (granted.containsAll(permissions)) { // All permissions granted, proceed with data access } } if (!manager.hasAllPermissions(permissions)) { permissionsLauncher.launch(permissions) } ``` -------------------------------- ### Health Services Repository for Passive Goals Source: https://context7.com/android/health-samples/llms.txt Manages subscriptions to passive monitoring clients for daily goals. Requires a Context for initialization and handles capabilities checks, subscriptions, and unsubscriptions. ```kotlin import androidx.health.services.client.data.* import android.content.Context import androidx.health.services.client.HealthServices import kotlinx.coroutines.guava.await class HealthServicesRepository(context: Context) { private val passiveMonitoringClient = HealthServices.getClient(context).passiveMonitoringClient private val goals = setOf(dailyStepsGoal, floorsGoal) private val requiredDataTypes = goals.map { it.dataTypeCondition.dataType }.toSet() private val passiveListenerConfig = PassiveListenerConfig( dataTypes = requiredDataTypes, shouldUserActivityInfoBeRequested = false, dailyGoals = goals, healthEventTypes = setOf() ) suspend fun hasFloorsAndDailyStepsCapability(): Boolean { val capabilities = passiveMonitoringClient.getCapabilitiesAsync().await() return capabilities.supportedDataTypesPassiveGoals.containsAll(requiredDataTypes) } suspend fun subscribeForGoals() { passiveMonitoringClient.setPassiveListenerServiceAsync( PassiveGoalsService::class.java, passiveListenerConfig ).await() } suspend fun unsubscribeFromGoals() { passiveMonitoringClient.clearPassiveListenerServiceAsync().await() } } ``` -------------------------------- ### Subscribe to Fitness Data with Recording API Source: https://context7.com/android/health-samples/llms.txt Use the Google Play Services Fitness Local Recording Client to subscribe to fitness data types like steps, distance, and calories on mobile devices. Ensure the LocalRecordingClient is initialized. ```kotlin import com.google.android.gms.fitness.FitnessLocal import com.google.android.gms.fitness.LocalRecordingClient import com.google.android.gms.fitness.data.LocalDataType import com.google.android.gms.fitness.data.LocalField import com.google.android.gms.fitness.request.LocalDataReadRequest import java.time.ZonedDateTime import java.util.concurrent.TimeUnit class RecordingAPIonMobileViewModel( private val localRecordingClient: LocalRecordingClient ) : ViewModel() { val localDataTypes = listOf( LocalDataType.TYPE_STEP_COUNT_DELTA, LocalDataType.TYPE_DISTANCE_DELTA, LocalDataType.TYPE_CALORIES_EXPENDED ) // Subscribe to data types fun subscribeData(localDataType: LocalDataType) { localRecordingClient .subscribe(localDataType) .addOnSuccessListener { Log.i(TAG, "Successfully subscribed ${localDataType.name}!") } .addOnFailureListener { e -> Log.e(TAG, "Failed to subscribe ${localDataType}", e) } } // Read raw data fun readRawData(startTime: ZonedDateTime, endTime: ZonedDateTime) { val readRequest = LocalDataReadRequest.Builder() .read(LocalDataType.TYPE_STEP_COUNT_DELTA) .bucketByTime(1, TimeUnit.HOURS) .setTimeRange( startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS ) .build() localRecordingClient.readData(readRequest) .addOnSuccessListener { response.buckets.forEach { bucket -> bucket.dataSets.forEach { dataSet -> dataSet.dataPoints.forEach { dataPoint -> val steps = dataPoint.getValue(LocalField.FIELD_STEPS) println("Steps: $steps") } } } } .addOnFailureListener { e -> Log.e(TAG, "Error reading data", e) } } // Read aggregated data fun readAggregateData(startTime: ZonedDateTime, endTime: ZonedDateTime) { val readRequest = LocalDataReadRequest.Builder() .aggregate(LocalDataType.TYPE_STEP_COUNT_DELTA) .bucketByTime(1, TimeUnit.HOURS) .setTimeRange( startTime.toEpochSecond(), endTime.toEpochSecond(), TimeUnit.SECONDS ) .build() localRecordingClient.readData(readRequest) .addOnSuccessListener { response.buckets.forEach { bucket -> val startTimestamp = bucket.getStartTime(TimeUnit.SECONDS) val endTimestamp = bucket.getEndTime(TimeUnit.SECONDS) // Process aggregated bucket data } } } } // Initialize in Activity: class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val localRecordingClient = FitnessLocal.getLocalRecordingClient(this) // Use localRecordingClient in your ViewModel } } ``` -------------------------------- ### Troubleshooting: Not yet implemented crash Source: https://github.com/android/health-samples/blob/main/health-services/PassiveDataCompose/README.md This error indicates an outdated Health Services version on the emulator. Ensure you are using the latest Wear image and verify the Health Services versionCode. ```log E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.passivedatacompose, PID: 30333 java.lang.Exception: Not yet implemented at androidx.health.services.client.impl.internal.StatusCallback.onFailure(StatusCallback.kt:42) ``` -------------------------------- ### Verify Health Services Version Source: https://github.com/android/health-samples/blob/main/health-services/PassiveGoalsCompose/README.md Use this adb command to check the version code of the Health Services package on your device or emulator. ```bash adb shell dumpsys package com.google.android.wearable.healthservices | grep versionCode ``` -------------------------------- ### Read Exercise Sessions from Health Connect Source: https://context7.com/android/health-samples/llms.txt Retrieves a list of `ExerciseSessionRecord` within a specified time range. Ensure you have the necessary read permissions before calling this function. ```kotlin import androidx.health.connect.client.records.ExerciseSessionRecord import androidx.health.connect.client.request.ReadRecordsRequest import androidx.health.connect.client.time.TimeRangeFilter import java.time.Instant suspend fun readExerciseSessions(start: Instant, end: Instant): List { val request = ReadRecordsRequest( recordType = ExerciseSessionRecord::class, timeRangeFilter = TimeRangeFilter.between(start, end) ) val response = healthConnectClient.readRecords(request) return response.records } // Usage: val sessions = readExerciseSessions( start = Instant.now().minus(Duration.ofDays(7)), end = Instant.now() ) sessions.forEach { session -> println("Session: ${session.title}") println("Type: ${session.exerciseType}") println("Duration: ${Duration.between(session.startTime, session.endTime)}") } ``` -------------------------------- ### Troubleshooting: App crashes with 'Not yet implemented' Source: https://github.com/android/health-samples/blob/main/health-services/PassiveGoalsCompose/README.md This crash indicates an outdated Health Services version on the emulator. Ensure you are using the latest Wear image and verify the Health Services versionCode is at least 70695. ```log E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.passivegoalscompose, PID: 30333 java.lang.Exception: Not yet implemented at androidx.health.services.client.impl.internal.StatusCallback.onFailure(StatusCallback.kt:42) ``` -------------------------------- ### Service for Receiving Goal Achievements Source: https://context7.com/android/health-samples/llms.txt A service that extends PassiveListenerService to receive and process goal completion events. Implement the onGoalCompleted callback to handle specific goal achievements and display user notifications. ```kotlin import androidx.health.services.client.data.* // Service to receive goal achievements: class PassiveGoalsService : PassiveListenerService() { override fun onGoalCompleted(goal: PassiveGoal) { val notificationText = when (goal.dataTypeCondition.dataType) { DataType.STEPS_DAILY -> "Congratulations! You reached your daily step goal!" DataType.FLOORS_DAILY -> "Great job! You climbed your target floors today!" else -> "Goal achieved!" } // Show notification to user } } ``` -------------------------------- ### Stream Differential Data Changes Source: https://context7.com/android/health-samples/llms.txt Enables tracking of data modifications by obtaining a token and then streaming changes since that token was issued. This is useful for synchronizing data without re-fetching everything. Handle token expiration by requesting a new one. ```kotlin import androidx.health.connect.client.request.ChangesTokenRequest import kotlin.reflect.KClass import androidx.health.connect.client.records.Record // Get initial changes token suspend fun getChangesToken(dataTypes: Set>): String { val request = ChangesTokenRequest(dataTypes) return healthConnectClient.getChangesToken(request) } ``` ```kotlin import androidx.health.connect.client.changes.Change import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import java.io.IOException // Stream changes since last token suspend fun getChanges(token: String): Flow = flow { var nextChangesToken = token do { val response = healthConnectClient.getChanges(nextChangesToken) if (response.changesTokenExpired) { throw IOException("Changes token has expired") } emit(ChangesMessage.ChangeList(response.changes)) nextChangesToken = response.nextChangesToken } while (response.hasMore) emit(ChangesMessage.NoMoreChanges(nextChangesToken)) } sealed class ChangesMessage { data class NoMoreChanges(val nextChangesToken: String) : ChangesMessage() data class ChangeList(val changes: List) : ChangesMessage() } ``` ```kotlin import androidx.health.connect.client.records.StepsRecord import androidx.health.connect.client.records.WeightRecord // Usage: val token = getChangesToken(setOf(StepsRecord::class, WeightRecord::class)) // Store token for later use // Later, retrieve changes getChanges(storedToken).collect { when (it) { is ChangesMessage.ChangeList -> { it.changes.forEach { change -> // Process upsert or delete change } } is ChangesMessage.NoMoreChanges -> { // Save new token for next sync storedToken = it.nextChangesToken } } } ``` -------------------------------- ### Background Heart Rate Data Collection Source: https://context7.com/android/health-samples/llms.txt Implement background health data collection using the Passive Monitoring Client. This requires setting up a PassiveListenerService to receive data updates. ```kotlin import androidx.health.services.client.HealthServices import androidx.health.services.client.data.DataType import androidx.health.services.client.data.PassiveListenerConfig class HealthServicesRepository(context: Context) { private val healthServicesClient = HealthServices.getClient(context) private val passiveMonitoringClient = healthServicesClient.passiveMonitoringClient private val passiveListenerConfig = PassiveListenerConfig( dataTypes = setOf(DataType.HEART_RATE_BPM), shouldUserActivityInfoBeRequested = false, dailyGoals = setOf(), healthEventTypes = setOf() ) suspend fun hasHeartRateCapability(): Boolean { val capabilities = passiveMonitoringClient.getCapabilitiesAsync().await() return DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring } suspend fun registerForHeartRateData() { passiveMonitoringClient.setPassiveListenerServiceAsync( PassiveDataService::class.java, passiveListenerConfig ).await() } suspend fun unregisterForHeartRateData() { passiveMonitoringClient.clearPassiveListenerServiceAsync().await() } } // PassiveDataService implementation: class PassiveDataService : PassiveListenerService() { override fun onNewDataPointsReceived(dataPoints: DataPointContainer) { val heartRateData = dataPoints.getData(DataType.HEART_RATE_BPM) heartRateData.forEach { // Store or process background heart rate data repository.storeLatestHeartRate(it.value) } } } ``` -------------------------------- ### Read Sleep Sessions with Duration Aggregation Source: https://context7.com/android/health-samples/llms.txt Reads sleep sessions from the last 7 days and aggregates their total sleep duration using the Health Connect API. It retrieves session details and calculates the total sleep time for each session. ```kotlin // Read sleep sessions with duration suspend fun readSleepSessions(): List { val sessions = mutableListOf() val sleepSessions = healthConnectClient.readRecords( ReadRecordsRequest( recordType = SleepSessionRecord::class, timeRangeFilter = TimeRangeFilter.between( Instant.now().minus(Duration.ofDays(7)), Instant.now() ), ascendingOrder = false ) ) sleepSessions.records.forEach { session -> val aggregateResponse = healthConnectClient.aggregate( AggregateRequest( metrics = setOf(SleepSessionRecord.SLEEP_DURATION_TOTAL), timeRangeFilter = TimeRangeFilter.between(session.startTime, session.endTime) ) ) sessions.add( SleepSessionData( uid = session.metadata.id, startTime = session.startTime, endTime = session.endTime, duration = aggregateResponse[SleepSessionRecord.SLEEP_DURATION_TOTAL], stages = session.stages ) ) } return sessions } ``` -------------------------------- ### Delete Exercise Session and Related Data Source: https://context7.com/android/health-samples/llms.txt Deletes a specific exercise session and all associated data records like heart rate, steps, and distance within its time range. Ensure you have the necessary permissions before calling this function. ```kotlin suspend fun deleteExerciseSession(uid: String) { val exerciseSession = healthConnectClient.readRecord(ExerciseSessionRecord::class, uid) // Delete the session record healthConnectClient.deleteRecords( ExerciseSessionRecord::class, recordIdsList = listOf(uid), clientRecordIdsList = emptyList() ) // Delete all related data within the session's time range val timeRangeFilter = TimeRangeFilter.between( exerciseSession.record.startTime, exerciseSession.record.endTime ) val rawDataTypes: Set> = setOf( HeartRateRecord::class, SpeedRecord::class, DistanceRecord::class, StepsRecord::class, TotalCaloriesBurnedRecord::class ) rawDataTypes.forEach { rawType -> healthConnectClient.deleteRecords(rawType, timeRangeFilter) } } // Usage: deleteExerciseSession("session-uid-123") ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.