반응형
🖐 Wear OS | 폰-워치 동기화와 큰절 제스처 시작 표식, 설정 화면 표시까지

개요 (Intro)
- 오늘의 목표 / 배경: 폰앱 설정 화면에서 큰절 인식 시작 버튼을 누르면, 워치앱 UI에도 즉시 시작 표식이 뜨고, 워치의 센서 변화(스텝/제스처 결과)를 폰에서 수신/표시
- 어떤 문제를 해결하려 했는지: Wear 메시지/데이터 동기화 경로 정리, 네임스페이스/매니페스트 충돌로 인한 ClassNotFound와 Compose/KSP 설정 이슈 정리
- 사용한 기술 스택: Kotlin, Jetpack Compose, Hilt, Google Play services wearable(Message/DataClient), Health Services(steps), Health Connect, KSP
📅 날짜: 2025.11.20
🎯 목표: 폰↔워치 제스처/스텝 동기화 + 워치 시작 표식 + 설정 화면 표시
🧰 기술: Kotlin, Compose, Hilt, Wearable APIs, Health Services, Health Connect
문제 정의 (Problem / Motivation)
- Wear 모듈 applicationId/패키지 정리 중, AndroidManifest의 application name이 상대 경로로 지정되어 ClassNotFoundException 발생
- Wear 빌드 시 Compose/KSP 설정 누락으로 컴파일 에러 발생
- 폰↔워치 간 스텝/제스처 전송 경로(메시지/데이터 아이템) 구성과 UI 상태 연동 필요
- 에뮬레이터에서 자이로/스텝 센서 이벤트를 어떻게 테스트할지 가이드 필요
java.lang.ClassNotFoundException: com.billcoreatech.health501.WearAppApplication
(매니페스트 상대경로 지정으로 FQCN 불일치)
해결 과정 (How I Solved It)
- Wear 메시지 수신 리스너(GestureCommandReceiver)에 START/STOP 상태 콜백(onState) 추가 → 워치 Compose 상태 업데이트로 시작 표식 노출
- 폰 ViewModel(HealthConnectViewModel)에 lastRemoteStep/gestureLogState 제공 → 설정 화면(SettingsScreen)에서 수신 값 즉시 표시
- Wear Manifest의 application/activity를 FQCN으로 명시해 ClassNotFound 해결
- 빌드 설정: Kotlin 2.x + Compose Compiler 플러그인 적용, KSP/플러그인 버전 정합성 검증
// wear GestureCommandReceiver: START/STOP 수신 시 UI에 상태 전달
class GestureCommandReceiver(
private val context: Context,
private val scope: CoroutineScope,
private val onState: ((started: Boolean, trials: Int?) -> Unit)? = null
) : MessageClient.OnMessageReceivedListener {
// ...existing code...
private fun start(trials: Int) {
stop()
onState?.invoke(true, trials)
// BowGestureManager 시작 및 결과 전송
}
private fun stop() {
// ...existing code...
onState?.invoke(false, null)
}
}
// wear MainActivity - Compose UI: 시작 표식 노출
@Composable
fun WearApp() {
var gestureStarted by remember { mutableStateOf(false) }
var gestureTrials by remember { mutableStateOf<Int?>(null) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
val gestureReceiver = remember(context) {
GestureCommandReceiver(context, scope) { started, trials ->
gestureStarted = started
gestureTrials = if (started) trials else null
}
}
LaunchedEffect(Unit) { gestureReceiver.register() }
DisposableEffect(Unit) { onDispose { gestureReceiver.unregister() } }
if (gestureStarted) {
Text("큰절 인식 시작 x${gestureTrials ?: ""}")
}
}
// phone SettingsScreen - 워치 센서 최신 상태 & 제스처 결과 표시
@Composable
fun SettingsScreen(viewModel: HealthConnectViewModel = hiltViewModel()) {
val lastRemote = viewModel.lastRemoteStep.collectAsState().value
val gestureLogs = viewModel.gestureLogState.collectAsState().value
Button(onClick = { viewModel.startGestureRecognition(trials = 5) }) {
Text("큰절 동작 인식 시작하기 (x5)")
}
lastRemote?.let {
Text("워치 센서 최신 상태: steps=${it.steps}, Δ=${it.delta}, day=${it.dayKey}, src=${it.source}")
}
if (gestureLogs.isNotEmpty()) Text("최근 제스처 결과: ${gestureLogs.last()}")
}
<application
android:name="com.billcoreatech.health501.wear.WearAppApplication"
... >
<activity android:name="com.billcoreatech.health501.wear.MainActivity" />
</application>
결과 (Result)
- 폰 설정 화면에서 “큰절 동작 인식 시작하기” 클릭 → 워치 화면에 즉시 “큰절 인식 시작 xN” 표식 노출
- 워치 센서(스텝) 상태 변동과 제스처 결과가 폰 설정 화면에 실시간으로 표시
- Wear Manifest FQCN 고정으로 ClassNotFoundException 제거
- Compose/KSP/Compose Compiler 설정 정리로 Wear 빌드 안정화
✅ 폰↔워치 메시지 라우팅 안정화
✅ 시작 표식/수신 로그로 사용자 피드백 강화
✅ 리소스/네임스페이스/플러그인 구성 정합성 개선
느낀 점 / 회고 (Reflection)
- Wear와 Phone의 applicationId를 동일하게 맞출 경우, 매니페스트 상대 경로가 곧바로 런타임 크래시로 이어질 수 있음을 재확인
- 센서 이벤트를 UI 상태로 끌어올릴 때, 메시지 수신 스레드와 Compose 상태 업데이트 사이의 경계 관리가 중요
- 에뮬레이터에서는 센서 이벤트가 빈약할 수 있어, 개발용 모의 이벤트/로그를 준비하면 디버깅 생산성이 올라감
참고자료 (References)
반응형
'모바일 앱(안드로이드)' 카테고리의 다른 글
| 🛰️ Android | 워치-폰 동기화 + 위치(x,y,z)+고도 융합(칼만/EMA) 기능 통합 작업 기록 (0) | 2025.12.03 |
|---|---|
| 🛠 Wear + Phone Altitude 동기화 & 설정화면 컴파일 오류 해결 요약 (1) | 2025.12.01 |
| 🛠 Android | Coupang API + Hilt DI + AdsScreen UX/포맷 개선 작업 기록 (1) | 2025.11.25 |
| 🦾 Android | 하단 바 + Navigation Compose + 보안 키 주입(ResValue) 적용기 (1) | 2025.11.23 |
| 🩺 Android | Health Connect 걸음 수 집계 캐시 & 상단바 최소 높이 적용 (1) | 2025.11.21 |