Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

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

Billcorea 2025. 12. 23. 15:42
반응형

 

🦾 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 미지원 단말에서 크래시 발생.
  • 웨어에서 PermissionController unresolved 및 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) 빌드/경고 정리

  • 웨어 PermissionController unresolved 제거(HC 코드 삭제).
  • 웨어 타일/LocationListener deprecation 경고는 @Suppress / 주석 보강으로 억제.
  • 런타임 크래시(‘SDK too low...’)는 웨어 HC 호출 제거로 근본 해결.

결과 (Result)

  • 폰: HC 권한 승인 후 자동으로 MainScreen 진입, 오늘 걸음수 정상 집계.
  • 웨어: HC 비의존 구조로 안정화, 위치/고도/걸음수 동작 및 동기화 유지.
  • 메인 화면: 걸음수 단일 지표 + 단일 라인(원 단위)로 깔끔하게 표시.
✅ 권한/동기화 흐름 안정화 및 크래시 제거
📈 그래프 단일화(+ 가독성 향상), 값 스케일(÷10) 제거
⌚ 웨어는 센서 기반, 폰은 HC 기반으로 역할 분담 명확화

느낀 점 / 회고 (Reflection)

  • 웨어에서 HC 미지원임을 초기에 가설로 두고 아키텍처를 분리했어야 했다.
  • “표시는 하나, 집계는 중앙(폰)에서” 원칙이 동기화 복잡도를 줄였다.
  • 다음에는 그래프 축/단위 포맷(천단위 구분, 축 라벨)을 더 친절하게 개선하고 싶다.

참고자료 (References)

반응형