Today's

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

모바일 앱(안드로이드)

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

Billcorea 2025. 11. 29. 15:39
반응형

 

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

wear 와 phone 동기화 준비중

개요 (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)

반응형