실버케어 플랫폼 프로젝트

[Android]헬스커넥트 연동 앱 만들기

gotopm 2025. 4. 3. 20:19
개발이유

 

팀 프로젝트에서 핵심적인 기능이 스마트워치(갤럭시 워치)에 있는 건강 데이터를 스프링 서버로 가지고와 사용자에게 부가적인 서비스를 제공하는 것이었다.

그러나 문제는 삼성헬스에서 내 스프링 서버로 데이터를 받는 API는 없다.

갤럭시워치에 쌓이는 데이터는 내 모바일 앱의 삼성헬스에 연동되는데 그 데이터는

다시 헬스커넥트(삼성과 구글에서 만든 앱)에 연동된다.

그래서 건강데이터를 스프링 서버로 가지고 오는 유일한 방법은  헬스커넥트의 데이터를 가지고와서 우리 스프링 서버로 보내주는 앱을 만드는 것이다.

 

다만, 스프링 위주의 웹개발만 해봤을 뿐 앱개발과 코틀린은 처음이었다.

그래서 아래 나같은 사람을 위해 차근차근(?) 설명해놓았으니 GPT에게 도움을 받아가면 충분히 할 수 있을 것이다.


 

1.안드로이드 스튜디오 설치

https://developer.android.com/studio?hl=ko

 

Android 스튜디오 및 앱 도구 다운로드 - Android 개발자  |  Android Studio  |  Android Developers

Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.

developer.android.com

 

2.프로젝트 만들기

 

설치받은 안드로이드 스튜디오 실행 -> New Project -> Empty Activity 선택 -> 언어 : Kotlin -> Minumm SDK : AP 26(Android 8.0) 이상 선택

프로젝트가 생성되면 크게 3가지 파일에 대해 알면된다.

  • MainActivity.kt           ->화면과 로직을 담당하는 파일
  • AndroidManifest.xml  -> 앱 권한에 대한 설정
  • build.gradle.kts                -> 의존성 설정(스프링의 build.gradle과 거의 똑같은 듯 하다)

3. 내가 만들 안드로이드 앱을 폰에 실행하기 위한 세팅

 

스마트폰에서 설정->휴대전화 정보->소프트웨어 정보->빌드번호 탭을 7번 터치한다

그러면 설정에 다시 들어가보면 제일 아랫쪽에 개발자 옵션 메뉴가 생겨있는데 개발자 옵션에 들어가 USB 디버깅을 눌러 활성화 시키면 된다. 그리고 노트북 또는 데스크톱으로 usb 연결을 하면 안드로이드 스튜디오에서 내 폰을 인식하여 폰에다가 앱을 설치하고 실행할 수 있게 된다.

 

4. 코드 구현

우리가 만들어야 할 로직은 아래와 같다.

1.헬스커넥트에서 데이터를 들고온다.

2.데이터를 잘 가져왔는지 확인하기 위해 앱 화면에 데이터를 보여준다.

3.가져온 데이터를 내 스프링 서버에 보낸다.

 

4-1. 의존성 주입

그래서 필요한 의존성을 build.gradle.kts(Module :app)에 주입받아야한다.

(헬스커넥트 API의존성, Retrofit의존성,coroutine의존성,JetPack Compose의존성)

//    추가한 의존성들
//    헬스커넥트 API
    implementation("androidx.health.connect:connect-client:1.1.0-beta01")
//    Retrofit (서버 통신용)
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
//   비동기 실행
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
//  로그 확인용
    implementation("com.jakewharton.timber:timber:5.0.1")

    // Jetpack Compose 의존성 추가
    implementation("androidx.compose.ui:ui:$compose_version")
    implementation("androidx.compose.material3:material3:1.1.0")
    implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
    debugImplementation("androidx.compose.ui:ui-tooling:$compose_version")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1")
    implementation("androidx.activity:activity-compose:1.3.1")
    // Jetpack Compose 라이프사이클
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.1")

 

Retrofit은 안드로이드에서 서버와의 통신(HTTP요청/응답)을 쉽게 해주는 라이브러리이고, coroutine은 비동기실행을 위한 것,

JetPack은  UI을 간단하게 구현하기 위한 의존성이다. 

그리고 상단에 생기는 Sync Now버튼을 눌러 Gradle을 동기화해준다

 

4-2.권한 추가

그래서  AndroidManifest.xml파일에  헬스커넥트에서 가져올 항목에 대한 권한을 추가해야한다.

그리고 서버통신을 위해서 인터넷에 대한 권한도 추가해준다.

https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types?hl=ko

 

헬스 커넥트 데이터 유형 계획 및 검토하기  |  Android health & fitness  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 헬스 커넥트 데이터 유형 계획 및 검토하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 헬스 커넥

developer.android.com

(그 외에 헬스커넥트에서 가져올 항목에 대해서 따로 권한을 추가해야한다.

가져올 항목과 추가해야할 권한에 대해서는 아래 공식 홈페이지에서 확인할 수 있다.)

 

그리고 아래 코드를 보면 <--권한 요청 관련 필터 추가-->와 <--acivity-alias 추가 --> 부분이 있는데

그쪽 코드를 추가해야 내 앱이 헬스커넥트에 권한을 요청하는 화면이 휴대폰에서 구현되었다.(안드로이드 14기준)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
<!--    HealthConnect의 심박수 데이터에 접근할 수 있는 권한-->
    <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
    <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" />
<!--    걸음수 데이터에 접근할 수 있는 권한-->
    <uses-permission android:name="android.permission.health.READ_STEPS" />
    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
<!--    전체소모칼로리 데이터에 접근할 수 있는 권한-->
    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
    <!--    오늘 걸은 거리 데이터에 접근할 수 있는 권한-->
    <uses-permission android:name="android.permission.health.READ_DISTANCE" />
    <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
    <!--    전체소모칼로리 데이터에 접근할 수 있는 권한-->
    <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
    <uses-permission android:name="android.permission.health.WRITE_ACTIVE_CALORIES_BURNED" />
<!--    나중에 서버통신을 위해 인터넷 권한도 설정(스프링서버로 HTTP통신하기 위함)-->
    
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SilverPotion"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.SilverPotion">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <!-- 권한 요청 관련 필터 추가 -->
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>
        <!-- activity-alias 추가 -->
        <activity-alias
            android:name=".AndroidURationaleActivity"
            android:exported="true"
            android:targetActivity=".MainActivity"
            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>
    </application>

    <!-- 헬스커넥트 앱이 설치되어 있는지 확인하는 쿼리 -->
    <queries>
        <package android:name="com.google.android.apps.healthdata" />
    </queries>


</manifest>

 

4-3. HealthConnectManager.kt 만들기

com.example.프로젝트명 패키지 안에 Kotlin파일 을 생성한다.

여기 코틀린 파일은 헬스커넥트에 연결해서 데이터를 가지고 오기 위해 함수를 세팅하는 곳이다.

아래 코드는 여러 데이터를 들고 와 길어보이지만 한 가지 구조만 보면 간단하다.

(심박수-걸음수-총소모칼로리-걸은 거리-활동소모칼로리를 가지고 오는 구조로 한가지만 보면 간단)

 

package com.example.silverpotion

import android.content.Context
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId

class HealthConnectManager(private val context: Context) {
//    생성자에서 Context타입의 값을 받아서 클래스안에 context라는 이름으로 읽기 전용(private val)로 저장함(val은 변경할 수 없는 값)
//   Context는 안드로이드에서 현재 앱,화면,컴포넌트에 대한 정보
//    HealthConnectClient는 앱에서 헬스커넥트 기능을 사용하기 위한 객체
//    내 휴대폰에 있는 헬스커넥트 앱과 연결해서 클라이언트를 만드는 코드
    val healthConnectClient = HealthConnectClient.getOrCreate(context)

//  setOf는 여러 개의 값을 가진 집합자료형 함수
//    HeartRateRecord::class 는 HeartRateRecord 클래스 자체를 가리킴
    val permissions = setOf(
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
        HealthPermission.getReadPermission(DistanceRecord::class),
        HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class),
    )



// suspend->코루틴(비동기처리)에서 사용할 수 있는 함수 , fun은 함수 선언 , :List<HeartRateRecord>는 반환 타입
    suspend fun readHeartRates(): List<HeartRateRecord> {
        val now = Instant.now() //현재시간
        val todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant() // 오늘 자정 시간 기준

        val request = ReadRecordsRequest(
            recordType = HeartRateRecord::class, //어떤 종류의 기록을 읽을건지(HeartRateRecord)
            timeRangeFilter = TimeRangeFilter.between(todayStart, now) //어느 시간 범위를 읽을 건지->여기선 자정부터 현재
        )
        return healthConnectClient.readRecords(request).records//요청객체를 사용해서 위에서 만든request를 주고 records를 반환받음
    } //즉 이 앱이 readHeartRates함수를 호출하면, List<HeartRateRecord>에 데이터가 담기는 것\

    // 걸음수 데이터를 읽는 함수
    suspend fun readStepCounts(): List<StepsRecord> {
        val now = Instant.now()
        val todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant() // 오늘 자정 시간 기준

        val request = ReadRecordsRequest(
            recordType = StepsRecord::class, // 걸음수 데이터를 읽음
            timeRangeFilter = TimeRangeFilter.between(todayStart, now) // 시간 범위 지정
        )
        return healthConnectClient.readRecords(request).records
    } //즉 readStepCounts()가 실행되면 헬스커넥트로 부터 걸음수데이터를 받아와 List<StepsRecord>에 담기는 거

    suspend fun readCaloriesBurned(): List<TotalCaloriesBurnedRecord> {
        val now = Instant.now() // 현재 시간
        val todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant() // 오늘 자정 시간 기준

        val request = ReadRecordsRequest(
            recordType = TotalCaloriesBurnedRecord::class, // 칼로리 소모량 기록
            timeRangeFilter = TimeRangeFilter.between(todayStart, now) // 어제부터 오늘까지의 데이터
        )
        return healthConnectClient.readRecords(request).records // 데이터를 반환
    } //오늘 소모 칼로리를 받아오는 함수

    suspend fun readDistanceWalked(): List<DistanceRecord> {
        val now = Instant.now()
        val todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant() // 오늘 자정 시간 기준

        val request = ReadRecordsRequest(
            recordType = DistanceRecord::class,
            timeRangeFilter = TimeRangeFilter.between(todayStart, now)
        )
        return healthConnectClient.readRecords(request).records
    } // 오늘 걸은 거리를 받아옴

    suspend fun readActiveCaloriesBurned(): List<ActiveCaloriesBurnedRecord> {
        val now = Instant.now()
        val todayStart = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()

        val request = ReadRecordsRequest(
            recordType = ActiveCaloriesBurnedRecord::class,
            timeRangeFilter = TimeRangeFilter.between(todayStart, now)
        )

        return healthConnectClient.readRecords(request).records
    }




}

 

4-4. Retrofit관련 세팅하기

com.example.프로젝트명 폴더에서 network라는 폴더를 만들고 network폴더 아래에 ApiService.kt라는 파일 코틀린 파일을 생성한다.  ApiService.kt파일은 우리가 Retrofit을 사용할 때 어떤 HTTP요청을 보낼지 정의하는 인터페이스파일이다.

다시 말해서 Retrofit을 통해 서버와 통신할 함수(API 요청)를 선언하는 곳이다.

 

package com.example.silverpotion.network

import com.example.silverpotion.HealthData
import retrofit2.http.Body
import retrofit2.http.POST


interface ApiService {

    @POST("/hh/receive")
    suspend fun sendHealthData(@Body healthData: HealthData )
}

 

그런 다음, network파일에 RetrofitClient.kt파일을 만들고 아래 코드를 붙여 넣는다.

여기는 서버로 데이터 요청을 보낼 Retrofit 을 설정하는 곳으로 아래 코드 중

private const val BASE_URL = "    " 이곳에 자신의 서버 주소를 입력하면 그 서버로 데이터를 보내게 설정하는 것이다

Retrofit으로 보낼 서버는 반드시 HTTPS 인증서 처리가 된 채로 배포가 된 상태여야 한다.

package com.example.silverpotion.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

//여기는 RetrofitClient인스턴스를 설정하는 코드
//object는 코틀린 전역에서 하나만 존재하는 싱글톤 객체를 정의할 떄 사용,
object RetrofitClient {
//    서버의 기본 주소를 저장하는 상수
    private const val BASE_URL = "https://server.jy1187.shop" // 여기에 실제 서버 주소 입력
// apiService는 ApiService타입의 변수 by lazy는 처음 사용할 때 한 번만 실행하고 저장되는 방식
    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

 

4-5. MainActivity.kt에서 헬스커넥트로부터 데이터를 받아오고 서버로 보내는 메인 로직 짜기

 

코틀린 문법이 처음이라 정리가 어려웠던 부분이다.

중간중간에 로그를 찍어놓은 부분이 있어 에러가 터지면 어디서 에러가 터졌는지 쉽게 파악할 수 있다.

로그는 안드로이드 스튜디오 아래에 고양이 아이콘의 로그캣을 누르고 HEALTH_SYNC로 필터를 걸어 쉽게 찾을 수 있다.

 

 

1.permissionLauncher부분은 사용자에게 헬스커넥트에 대한 권한을 요청하고 권한이 승인되면 아래에 정의된 fetchAndSend 함수를 호출한다.

2.onCreate함수가 이 MainActivity가 실행될 때 가장 처음으로 실행되는 함수로서 헬스커넥트와 통신할 수 있는 HealthConnectManager라는 객체를 생성한다.

3.SetContent부분은 화면의 UI를 담당하는 곳이다.

4.fetchAndSend함수가 HealthConnectManager를 이용해 헬스커넥트에 있는 데이터를 불러오고 HealthData라는 객체를 만들어 거기에다 데이터를 넣어 우리가 원하는 스프링서버로 보내는 함수이다. 

 

package com.example.silverpotion

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.health.connect.client.PermissionController
import androidx.lifecycle.lifecycleScope
import com.example.silverpotion.network.HeartRateData
import com.example.silverpotion.network.RetrofitClient
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

    private lateinit var healthConnectManager: HealthConnectManager

    // stepCountData를 클래스의 멤버 변수로 선언
    private var stepCountData by mutableStateOf<List<Int>>(emptyList())
    private var dailyCaloriesBurnedData by mutableStateOf(0.0) //총 소모칼로리
    private var distanceWalkedData by mutableStateOf(0.0) //오늘 걸은 거리
    private var activeCaloriesBurnedData by mutableStateOf(0.0) //활동으로 인한 소모칼로리


    private val permissionLauncher =
        registerForActivityResult<Set<String>, Set<String>>(
            PermissionController.createRequestPermissionResultContract() //헬스커넥트 권한 요청 처리
        ) { granted: Set<String> -> //granted되면 권한리스트에 들어감
            Log.d("HEALTH_SYNC", "권한 요청 결과: $granted")
            if (granted.containsAll(healthConnectManager.permissions)) {
                fetchAndSend { stepData,dailyCaloriesBurned,distanceWalked,activeCaloriesBurned  ->
                    stepCountData = stepData
                    dailyCaloriesBurnedData = dailyCaloriesBurned
                    distanceWalkedData = distanceWalked
                    activeCaloriesBurnedData = activeCaloriesBurned
                    // 여기서 stepData와 heartRateData를 사용할 수 있게 됨.
                    // UI에 데이터를 업데이트하는 작업을 여기에 추가하면
                }
            } else {
                Log.e("HEALTH_SYNC", "권한 요청 실패")
            }
        }
//permissionLauncher가 헬스커넥트 권한을 요청하고 결과를 처리하는 객체

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("HEALTH_SYNC", "앱 실행됨")
        healthConnectManager = HealthConnectManager(this)


// Jetpack Compose로 UI 구성
        setContent {

            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "실버포션",
                    style = MaterialTheme.typography.headlineLarge.copy(
                        fontWeight = FontWeight.Bold, // 글씨 굵게 설정
                        color = Color.Blue // 텍스트 색상을 파란색으로 설정
                    ),  // 큰 글씨 스타일 적용
                    modifier = Modifier.padding(bottom = 32.dp) // 하단 여백 추가
                )


                // 걸음수만 표시하는 텍스트 추가
                Text(text = "걸음수 데이터: ${stepCountData.joinToString(", ")} 보")
                // 칼로리 소모량만 표시하는 텍스트 추가
                Text(text = "칼로리 소모량: ${"%.2f".format(dailyCaloriesBurnedData)} kcal")
                // 거리 출력(미터기준)
                Text(text = "오늘 걸은 거리: ${"%.2f".format(distanceWalkedData)} m")
                Text(text = "활동 칼로리: ${"%.2f".format(activeCaloriesBurnedData)} kcal")
                Button(onClick = {
                    // 버튼 클릭 시 fetchAndSend() 실행
                    permissionLauncher.launch(healthConnectManager.permissions)
                }) {
                    Text(text = "데이터 가져오기 및 서버 전송")
                }
            }
        }
    }


    private fun fetchAndSend(onDataFetched: (List<Int>, Double, Double, Double) -> Unit) { //서버로 데이터 전송하는 함수
//        onDataFetched: (List<Int>, List<HeartRateData>) -> Unit는 함수 타입선언부분 List<Int>, List<HeartRateData>두 가지 탑을 인자로 받는 함수타입선언. Unit은 반환값이 없다는 의미
        Log.d("HEALTH_SYNC", "fetchAndSend 실행됨")
        lifecycleScope.launch {
            try {
//                걸음수 데이터 읽기
                val stepRecords =healthConnectManager.readStepCounts()
                val stepData = stepRecords.map{it.count.toInt()} //itdms stepRecords리스트의 각각의 요소.it은 람다 함수에서 사용하는 기본 파라미터
//                 칼로리 소모량 데이터 읽기
                val caloriesBurnedRecords = healthConnectManager.readCaloriesBurned()
                val caloriesBurnedData = caloriesBurnedRecords.sumOf { it.energy.inCalories }
                val dailyCaloriesBurned = caloriesBurnedData
//                심박수데이터읽기
                val heartRateRecords =healthConnectManager.readHeartRates()
                val heartRateData = heartRateRecords.map{
                    val sample = it.samples.firstOrNull()
                    HeartRateData(
                        bpm = sample?.beatsPerMinute?.toDouble() ?:0.0,
                        time = sample?.time.toString() //bpm:xx.x ,time: 2025-03-39 몇시 이런식으로 json형식
                    )
                }
//               오늘 걸은 거리
                val distanceRecords = healthConnectManager.readDistanceWalked()
                val totalDistance = distanceRecords.sumOf { it.distance.inMeters }
//              활동으로 소모한 칼로리
                val activeCalorieRecords = healthConnectManager.readActiveCaloriesBurned()
                val activeCaloriesBurned = activeCalorieRecords.sumOf { it.energy.inCalories }

                Log.d("HEALTH_SYNC", "걸음수 데이터: ${stepData.joinToString(", ")}")
                Log.d("HEALTH_SYNC", "심박수 데이터: ${heartRateRecords.size}개")
                Log.d("HEALTH_SYNC", "총 소모 칼로리 레코드 수: ${caloriesBurnedRecords.size}")
                Log.d("HEALTH_SYNC", "걸은 거리 레코드 수: ${distanceRecords.size}")
                Log.d("HEALTH_SYNC", "활동 칼로리 레코드 수: ${activeCalorieRecords.size}")
                Log.d("HEALTH_SYNC", "칼로리 소모량 데이터: $dailyCaloriesBurned")
                Log.d("HEALTH_SYNC", "걸은 거리: $totalDistance m")
                Log.d("HEALTH_SYNC", "활동 칼로리: $activeCaloriesBurned")
                // onDataFetched 호출하면서 데이터를 UI에넘겨줌
                onDataFetched(stepData,dailyCaloriesBurned,totalDistance,activeCaloriesBurned)


                val healthData = HealthData( //아레에서 HealthData 코틀린클래스를 정의함
                                stepData = stepData,
                                heartRateData = heartRateData,
                                caloriesBurnedData = dailyCaloriesBurned,
                                distanceWalked = totalDistance,
                                activeCaloriesBurned = activeCaloriesBurned
                            )
                // 서버로 전송
               RetrofitClient.apiService.sendHealthData(healthData)
            } catch (e: Exception) {
                Log.e("HEALTH_SYNC", "에러 발생", e)
            }
        }
    }
}


// HealthData 클래스를 정의
data class HealthData(
    val stepData: List<Int>,
    val heartRateData: List<HeartRateData>,
    val caloriesBurnedData: Double,
    val distanceWalked: Double,
    val activeCaloriesBurned: Double
)

 


 

코드가 길어 복잡해보일 수 있지만, 

일단 제일 처음 심박수 데이터만 가져와서 전송하는 것을 테스트해서 한 사이클 돌려보고 다른 데이터도 똑같은 방식으로 하면

금방 할 수 있을 것이다.

https://github.com/jykim1187/BringHealthConnectData

 

GitHub - jykim1187/BringHealthConnectData: 헬스커넥트에 있는 데이터를 받아와 웹서버로 보내기 위해 개발

헬스커넥트에 있는 데이터를 받아와 웹서버로 보내기 위해 개발한 중계 앱. Contribute to jykim1187/BringHealthConnectData development by creating an account on GitHub.

github.com

 

'실버케어 플랫폼 프로젝트' 카테고리의 다른 글

[FireBase]FCM 앱-스프링 연동  (1) 2025.04.08
[개념]WebRTC 개념 정리  (0) 2025.03.31