반응형
🦾 Android/Wear | Health Connect 권한·동기화 안정화 + 걸음수 단일화 및 그래프 정리

개요 (Intro)
- 오늘의 목표: 폰은 Health Connect로 걸음수 집계, 웨어는 센서 기반 수집 및 동기화에 집중. 메인 화면 걸음수·그래프를 단일화.
- 해결하고자 한 문제: 권한 흐름 막힘, 웨어의 HC 미지원/크래시, 걸음수 0 표시, 그래프 중복/스케일(÷10) 문제.
- 사용 스택: Kotlin, Jetpack Compose, Health Connect(1.1.x API), Wear OS, Horologist Tiles, Vico Charts, Hilt, Room
📅 날짜: 2025.12.23
🎯 목표: HC 권한/동기화 안정화, 폰/웨어 걸음수 단일 표시, 그래프 정리(원 단위)
🧰 기술: Kotlin, Compose, Health Connect, Wear OS, MVVM, Room, Hilt
문제 정의 (Problem / Motivation)
- 폰/웨어 권한 요청 UI가 제때 뜨지 않거나, HC 미지원 단말에서 크래시 발생.
- 웨어에서
PermissionControllerunresolved 및 HC API 호출 시SDK version too low or running in a profile예외. - 메인 화면 걸음수/그래프가 폰·웨어 두 라인으로 중복 표시되고, 값이 /10 스케일로 축소되어 혼란.
- 웨어에서 HC 직접 사용 불가. 대신 폰 HC를 기준으로 동기화해야 함.
// 런타임 예외(웨어): HC 초기화 시
java.lang.UnsupportedOperationException: SDK version too low or running in a profile
at androidx.health.connect.client.HealthConnectClient$Companion.getOrCreate(...)
해결 과정 (How I Solved It)
1) 폰: 권한 흐름 안정화 및 승인 후 메인 진입
권한 런처를 ActivityResult API로 구성하고, SDK_AVAILABLE일 때만 HC 권한을 요청하도록 분기했습니다. 승인되면 NavHost로 main으로 이동합니다.
// 파일: HealthConnectApp.kt (발췌)
// 1) Health Connect 권한 자동 요청 (가능할 때만)
LaunchedEffect(healthConnectManager.availability.value, permissionsGranted) {
if (healthConnectManager.availability.value == SDK_AVAILABLE && !permissionsGranted) {
permissionsLauncher.launch(permissions)
}
}
// 2) 활동(걸음수) 권한 자동 요청
LaunchedEffect(viewModel.hasPermission.value) {
if (!viewModel.hasPermission.value) {
launcher.launch(viewModel.permissionStep) // ACTIVITY_RECOGNITION
}
}
// 3) 두 권한 OK → 메인으로 이동
LaunchedEffect(permissionsGranted, viewModel.hasPermission.value) {
if (permissionsGranted && viewModel.hasPermission.value) {
viewModel.startStepTracking()
navController.navigate("main") {
popUpTo("permission") { inclusive = true }
launchSingleTop = true
}
}
}
2) 웨어: HC 의존 제거, 센서/동기화 중심으로 단순화
- 웨어에서 HC 관련 코드/권한/버튼을 완전히 제거.
- 걸음수는 웨어 로컬 센서로 폴백 수집 → 폰으로 전송 → 폰이 HC와 병합 → 병합 결과를 웨어에 재전송·표시.
- 고도는 웨어 기압 센서로 측정 → 폰으로 전송 → 폰 DB 저장 → 양쪽 화면에 표시.
// 파일: wear/MainActivity.kt (발췌, 개념 코드)
@Composable
fun WearApp() {
var localDaySteps by remember { mutableLongStateOf(0L) } // 웨어 로컬 폴백
var mergedDaySteps by remember { mutableLongStateOf(0L) } // 폰 병합 결과
val syncManager = remember(context) { WearDataSyncManager(context).apply {
listener = object : WearDataSyncManager.Listener {
override fun onRemoteSteps(remote: Long, merged: Long, payload: StepPayload) {
// 폰의 병합값을 우선 표시
mergedDaySteps = merged
}
}
}}
// 표시값: 병합값이 있으면 우선, 없으면 로컬 폴백
val displayedSteps = if (mergedDaySteps > 0L) mergedDaySteps else localDaySteps
Text(text = "오늘 걸음: $displayedSteps")
}
3) 메인 화면: 걸음수·그래프 단일화 + 스케일 제거
폰/웨어 스냅샷을 시간대별로 합산하여 한 개의 라인으로 표현하고, 이제 /10 스케일 제거로 원 단위 steps를 그대로 보여줍니다.
// 파일: app/presentation/MainScreen.kt (핵심 변경)
val stepsHourlyPhone = IntArray(24) { 0 }
val stepsHourlyWear = IntArray(24) { 0 }
// ... (todayKey로 오늘 데이터만 필터)
recentSteps.filter { it.dayKey == todayKey }.forEach { step ->
val hour = Instant.ofEpochMilli(step.timestamp).atZone(ZoneId.systemDefault()).hour
val d = step.delta.toInt().coerceAtLeast(0)
if (step.source.equals("phone", true)) stepsHourlyPhone[hour] += d else stepsHourlyWear[hour] += d
}
// 단일 시리즈 (원 단위)
val seriesStepsTotal = hours.map { h -> (stepsHourlyPhone[h] + stepsHourlyWear[h]).toFloat() }
lineSeries {
series(hours, xValuesForChart) // X
series(hours, yValuesForChart) // Y
series(hours, zValuesForChart) // Z
series(hours, seriesStepsTotal) // Steps Total
}
4) 빌드/경고 정리
- 웨어
PermissionControllerunresolved 제거(HC 코드 삭제). - 웨어 타일/LocationListener deprecation 경고는
@Suppress/ 주석 보강으로 억제. - 런타임 크래시(‘SDK too low...’)는 웨어 HC 호출 제거로 근본 해결.
결과 (Result)
- 폰: HC 권한 승인 후 자동으로 MainScreen 진입, 오늘 걸음수 정상 집계.
- 웨어: HC 비의존 구조로 안정화, 위치/고도/걸음수 동작 및 동기화 유지.
- 메인 화면: 걸음수 단일 지표 + 단일 라인(원 단위)로 깔끔하게 표시.
✅ 권한/동기화 흐름 안정화 및 크래시 제거
📈 그래프 단일화(+ 가독성 향상), 값 스케일(÷10) 제거
⌚ 웨어는 센서 기반, 폰은 HC 기반으로 역할 분담 명확화
느낀 점 / 회고 (Reflection)
- 웨어에서 HC 미지원임을 초기에 가설로 두고 아키텍처를 분리했어야 했다.
- “표시는 하나, 집계는 중앙(폰)에서” 원칙이 동기화 복잡도를 줄였다.
- 다음에는 그래프 축/단위 포맷(천단위 구분, 축 라벨)을 더 친절하게 개선하고 싶다.
참고자료 (References)
반응형
'모바일 앱(안드로이드)' 카테고리의 다른 글
| 🦾 Android | 재오픈 시 Android 뷰가 사라지는 문제 원인 분석 & 복구 자동화 (0) | 2025.12.27 |
|---|---|
| ⌚ Android Wear & Phone 연동 디버깅 | 고도 수집, 상태 동기화, Hilt 순환 참조 정리 (1) | 2025.12.25 |
| 🦾 Android | 메인 화면 뒤로가기 UX 개선, 워치/폰 걸음수 분리 표시, 설정 화면 카드화 (0) | 2025.12.21 |
| 🕹️ Android | Wear Compose UI 레이아웃 정리와 중앙정렬, 문자열 리소스화 (0) | 2025.12.17 |
| 🔍 프로젝트 진단 | Health501 아키텍처 & 코드 품질 개선 로드맵 (1) | 2025.12.15 |