반응형

 

Health Connect API 가이드 문서가 있지만.. java 기준이기도하고, 실제로 적용해보니 이슈가 몇가지생겨서 적용하는방법을 정리해보고자 한다.

 

가이드문서 URL : https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started?hl=ko

 

헬스 커넥트 시작하기  |  Android health & fitness  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 헬스 커넥트 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에서는 앱에서 헬스

developer.android.com

 

시작하기에 앞서 안드로이드 버전을 확인하면 좋은데, 나는 안드로이드 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문의에서 헬스커넥터로 데이터 안들어온다고 캡쳐화면이랑 문의글 올리면...들어오게 처리해준다 ㅠ 답변은 따로없다..다음날이나 몇시간 후 확인해보면됨..

728x90

+ Recent posts