Health Connect API 가이드 문서가 있지만.. java 기준이기도하고, 실제로 적용해보니 이슈가 몇가지생겨서 적용하는방법을 정리해보고자 한다.
가이드문서 URL : https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started?hl=ko
시작하기에 앞서 안드로이드 버전을 확인하면 좋은데, 나는 안드로이드 13버전으로 테스트 해보았고
이 경우 health connect 앱이 설치되어있지 않았고 설치 후에도 삼성헬스데이터가 넘어오지 않았다.
헬스커넥트 메인페이지 최근 정보에 엑세스한 앱에도 뜨지않았고 데이터 및 액세스 탭에도 데이터가 아예 없었다.
이 경우 삼성헬스 앱 - 상단 점세개 -> 문의하기 에서 헬스커넥트로 데이터연동 안되어있다고 문의하면 하루..정도 걸려서 해주신다..ㅎㅎ..참고..
1. 라이브러리 임포트 및 권한 추가
우선 build.gradle 에 health connect client 를 import 해준다
/*Health Connect*/
implementation "androidx.health.connect:connect-client:1.1.0-alpha02"
그리고 manifest 에 내가 취득하고싶은 권한을 넣는다.
권한종류에 관해선 가이드 문서에 상세히 나와있으니 참고하도록 하자.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<!--Health Connect Permissions-->
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.READ_SLEEP"/>
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_DISTANCE"/>
<uses-permission android:name="android.permission.health.READ_EXERCISE"/>
<uses-permission android:name="android.permission.health.READ_FLOORS_CLIMBED"/>
<!--Health Connect-->
<queries>
<package android:name="com.google.android.apps.healthdata" />
</queries>
...
</manifest>
내가 원하는 권한 종류들을 추가하였다
2. Health Connect API 호출
해당 API 를 호출하기 원하는 activity 혹은 fragment에서 호출하면 된다.
class MainFragment : Fragment() {
private var TAG : String = "MAIN FRAGMENT"
private var _binding : FragmentMainBinding? = null
private val binding : FragmentMainBinding
get() = _binding!!
private lateinit var healthConnectClient: HealthConnectClient
//manifest 에서 선언한 권한과 동일해야함
private val permissionList = setOf(
HealthPermission.getReadPermission(StepsRecord::class),
HealthPermission.getReadPermission(SleepSessionRecord::class),
HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class),
HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
HealthPermission.getReadPermission(DistanceRecord::class),
HealthPermission.getReadPermission(ExerciseSessionRecord::class),
HealthPermission.getReadPermission(FloorsClimbedRecord::class)
)
private val requestPermissions = registerForActivityResult(
PermissionController.createRequestPermissionResultContract()
) { granted ->
if (granted.containsAll(permissionList)) {
Log.d("MAIN ACTIVITY", "requestPermissions success ==== ")
CoroutineScope(Dispatchers.IO).launch {
readStepsData()
}
} else {
CustomToast.createToast(requireContext(), "건강정보 가져오기에 실패하였습니다.\n권한을 추가하거나 헬스 커넥트 앱을 다운로드 해주세요.")?.show()
Log.d("MAIN ACTIVITY", "requestPermissions fail ////////////////// ")
activity?.finish()
openPlayStoreForHealthConnect()
}
}
companion object {
fun newInstance(): MainFragment {
val fragment = MainFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding =
DataBindingUtil.inflate(layoutInflater, R.layout.fragment_main, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Health Connect
CoroutineScope(Dispatchers.Main).launch {
connectHealthData()
}
}
private fun connectHealthData() {
// 1. Health Connect 앱 유무 확인
val availabilityStatus = HealthConnectClient.getSdkStatus(requireContext(), Constants.HEALTH_CONNECT_PACKAGE_NAME)
if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
CustomToast.createToast(requireContext(), "헬스 커넥트 앱을 다운로드 해주세요")?.show()
activity?.finish()
openPlayStoreForHealthConnect()
}
// 2. Health connect sdk update 확인
if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
CustomToast.createToast(requireContext(), "헬스 커넥트 업데이트가 필요합니다")?.show()
activity?.finish()
openPlayStoreForHealthConnect()
}
// 4. Check permissions and run
healthConnectClient = HealthConnectClient.getOrCreate(requireContext())
requestPermissions.launch(permissionList)
}
private suspend fun readStepsData() {
val now: LocalDateTime = LocalDateTime.now()
val startOfDay = LocalDateTime.of(now.toLocalDate(), LocalTime.MIDNIGHT)
val request = ReadRecordsRequest(
recordType = StepsRecord::class,
timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
)
val response = healthConnectClient.readRecords(request)
val steps = response.records.sumOf { it.count }
//토탈 칼로리
val totalCaloriesRequest = ReadRecordsRequest(
recordType = TotalCaloriesBurnedRecord::class,
timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
)
val totalCaloriesResponse = healthConnectClient.readRecords(totalCaloriesRequest)
val totalCalories = totalCaloriesResponse.records.sumOf { it.energy.inCalories }
//활동 칼로리
val activeCaloriesRequest = ReadRecordsRequest(
recordType = ActiveCaloriesBurnedRecord::class,
timeRangeFilter = TimeRangeFilter.between(startOfDay, now),
)
val activeCaloriesResponse = healthConnectClient.readRecords(activeCaloriesRequest)
val activeCalories = activeCaloriesResponse.records.sumOf { it.energy.inCalories }
CoroutineScope(Dispatchers.Main).launch {
binding.tvStepCount.text = steps.toString()
binding.tvTotalCaloriesCount.text = totalCalories.toString()
binding.tvActiveCaloriesCount.text = activeCalories.toString()
}
}
private fun openPlayStoreForHealthConnect() {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata")
setPackage("com.android.vending")
}
startActivity(intent)
}
}
삼성헬스 데이터가 health connect 에 연결되었는가?
health connect 버전이 최신인가?
health connect 가 설치되어있는가?
등등 우선시 되어야 할 상황들을 체크하는 로직도 추가하였다.
3. Permission Activity 추가
이상하게 이걸 추가안하면..연결 계속 fail 뜬다.
아직 파악이 덜됨..
1) PermissionActivity.kt 추가
@AndroidEntryPoint
class PermissionActivity : AppCompatActivity(){
private lateinit var binding: ActivityPermissionBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPermissionBinding.inflate(layoutInflater)
setContentView(binding.root)
val permissions = setOf(
HealthPermissions.READ_STEPS
)
val permissionController = HealthConnectClient.getOrCreate(this).permissionController
//permissionController.pe(permissions, this)
}
private val permissionRequest = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
// 권한 요청 후의 결과 처리
// 필요한 경우 MainActivity로 돌아가게 함
startActivity(Intent(this, MainActivity::class.java))
}
2) layout 추가
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PermissionActivity">
<TextView
android:text="test - permission"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_area"
android:layout_width="match_parent"
android:layout_height="56dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/navigation_menu"
app:itemIconTint="@color/black"
app:labelVisibilityMode="labeled"
app:itemTextColor="@android:color/transparent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3) Manifest 수정
....
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.permission.PermissionActivity"
android:exported="true">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
...
해당코드는 깃헙에 올려두었다.
그런데, PermissionActivity 추가하는 로직에 관해선, 공식문서에 나와잇지만 안드로이드12이상부터는 아래와같이써야한다
<activity
android:name=".ui.permission.PermissionActivity"
android:exported="true">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
<activity-alias
android:name="ViewPermissionUsageActivity"
android:exported="true"
android:targetActivity=".ui.permission.PermissionActivity"
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>
아래 링크의 Fitness 프로젝트!
https://github.com/Daseul727/Mobile-Skill-Up
+
2024.10.29
하지만 결국..데이터 잘 안뽑혀서 삼성헬스 -> 점세개 -> 설정 -> 고객센터 -> 1:1문의에서 헬스커넥터로 데이터 안들어온다고 캡쳐화면이랑 문의글 올리면...들어오게 처리해준다 ㅠ 답변은 따로없다..다음날이나 몇시간 후 확인해보면됨..