Good afternoon! I develop Android application for educational purposes. What I want to do is to integrate it with Samsung Health App to retrieve some biomedical data, such as heart rate e.g.
The problem is AuthorizationException occurs on every HealthDataStore.getGrantedPermissions() or HealthDataStore.requestPermissions() call.
Setup:
- Device: Poco F3 with Android 13 (Developer mode is enabled)
- SDK: samsung-health-data-api-1.0.0-b2.aar
I would be grateful for any help
Thanks!
The code:
package com.slowdive.biodata.data
import android.app.Activity
import android.content.Context
import android.util.Log
import com.samsung.android.sdk.health.data.HealthDataService
import com.samsung.android.sdk.health.data.data.HealthDataPoint
import com.samsung.android.sdk.health.data.error.ResolvablePlatformException
import com.samsung.android.sdk.health.data.permission.AccessType
import com.samsung.android.sdk.health.data.permission.Permission
import com.samsung.android.sdk.health.data.request.DataType
import com.samsung.android.sdk.health.data.request.DataTypes
import com.samsung.android.sdk.health.data.request.LocalTimeFilter
import com.samsung.android.sdk.health.data.request.Ordering
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.time.LocalDateTime
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository for interacting with Samsung Health API
*/
@Singleton
class SamsungHealthRepository @Inject constructor(private val context: Context) {
private val TAG = "SamsungHealthRepository"
suspend fun getLatestHeartRate(): Int? {
return withContext(Dispatchers.IO) {
try {
processPermissions()
val now = LocalDateTime.now()
val oneDayAgo = now.minusDays(1)
val localTimeFilter = LocalTimeFilter.of(oneDayAgo, now)
val readRequest = DataTypes.HEART_RATE.readDataRequestBuilder
.setLocalTimeFilter(localTimeFilter)
.setOrdering(Ordering.DESC)
.setLimit(1)
.build()
val healthDataStore = HealthDataService.getStore(context)
val heartRateList = healthDataStore.readData(readRequest).dataList
if (heartRateList.isNotEmpty()) {
val latestHeartRate = processHeartRateData(heartRateList.first())
Log.d(TAG, "Latest heart rate: $latestHeartRate")
return@withContext latestHeartRate
} else {
Log.d(TAG, "No heart rate data found in the last 24 hours")
return@withContext null
}
} catch (e: Exception) {
Log.e(TAG, "Error reading heart rate data: ${e}")
return@withContext null
}
}
}
private suspend fun processPermissions(): Boolean {
return withContext(Dispatchers.IO) {
try {
val healthDataStore = HealthDataService.getStore(context);
val permissionsNeeded = mutableSetOf(Permission.of(DataTypes.HEART_RATE, AccessType.READ))
val grantedPermissions = healthDataStore.getGrantedPermissions(permissionsNeeded)
if (!grantedPermissions.containsAll(permissionsNeeded)) {
val result = healthDataStore.requestPermissions(permissionsNeeded, context as Activity)
return@withContext result.containsAll(permissionsNeeded)
}
return@withContext true
} catch (error: Exception) {
if (error is ResolvablePlatformException && error.hasResolution) {
error.resolve(context as Activity)
return@withContext false
}
return@withContext false
}
}
}
private fun processHeartRateData(heartRateData: HealthDataPoint): Int? {
return heartRateData.getValue(DataType.HeartRateType.HEART_RATE)?.toInt()
}
}
package com.slowdive.biodata.presentation
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.slowdive.SlowdiveApplication
import com.slowdive.biodata.data.SamsungHealthRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* ViewModel for the Biodata screen that handles biomedical data
*/
@HiltViewModel
class BiodataViewModel @Inject constructor(
private val samsungHealthRepository: SamsungHealthRepository
) : ViewModel() {
private val _heartRate = MutableStateFlow(40)
val heartRate: StateFlow<Int> = _heartRate
private val _stressLevel = MutableStateFlow(0.4f)
val stressLevel: StateFlow<Float> = _stressLevel
private val _bloodOxygenLevel = MutableStateFlow(98)
val bloodOxygenLevel: StateFlow<Int> = _bloodOxygenLevel
/**
* Sync biodata from Samsung Health
*/
fun syncBiodata() {
viewModelScope.launch {
try {
val heartRateValue = samsungHealthRepository.getLatestHeartRate()
heartRateValue?.let {
_heartRate.value = heartRateValue
}
} catch (e: Exception) {
Log.e(SlowdiveApplication.APP_TAG, "Error syncing biodata: ${e.message}")
}
}
}
}