# [Claude Code] Galaxy Watch 5에 러닝 코치 앱 빌드·배포까지 완성한 하루
---
## 소개
### 시도하고자 했던 것과 그 이유
저는 매주 일요일 러닝을 시작하고, 1년 뒤 하프마라톤(21.0975km) 완주를 목표로 하고 있습니다.
러닝할 때 Galaxy Watch 5에서 실시간 심박수·거리·페이스를 확인하고, 구간마다 알람(거리 도달 / 심박수 초과 / 페이스 저하 / 스피드업 존)을 받을 수 있는 앱이 필요했습니다. 기성 앱은 너무 복잡하거나 커스터마이징이 안 되어서 **Claude Code로 직접 Wear OS 앱을 만들어보기로** 했습니다.
어제(3/21) 기획·코드 뼈대를 완성한 뒤, 오늘은 **Android Studio에서 빌드 가능한 상태로 만들고 실기기에 APK 배포까지** 완료하는 것이 목표였습니다.
---
## 진행 방법
### 사용 도구
- **Claude Code** — 코드 분석, 오류 원인 파악, 수정, 문서 작성
- **Android Studio Meerkat** — Gradle Sync 및 빌드 실행
- **Galaxy Watch 5 (SM-R935N)** — Wi-Fi ADB로 실기기 배포 및 동작 확인
### 프롬프트 예시
> "에러가 나왔어 원인분석하고 조치해줘" + 빌드 에러 스크린샷
Claude Code에게 에러 화면을 첨부하면 원인을 분석하고 수정 코드까지 바로 제시해줍니다. 저는 이 방식으로 빌드 에러를 하나씩 해결했습니다.
---
### Step 1. Gradle Sync
어제 작성한 프로젝트 구조로 첫 Gradle Sync 실행.
```
BUILD SUCCESSFUL in 6m 57s
```
app / wear 두 모듈 모두 인식 확인.
---
### Step 2. AndroidManifest.xml 권한 보완
```xml
<!-- 백그라운드 심박수 수신 -->
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
<!-- 러닝 중 화면 꺼짐 방지 -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 메시지 경로 통합 (/goal, /records 등 분산 → / 하나로) -->
<data android:scheme="wear" android:host="*" android:pathPrefix="/" />
```
---
### Step 3. RunningRepository — Service ↔ ViewModel 브릿지 패턴
Android에서 Service는 Hilt ViewModel에 직접 주입할 수 없습니다.
`@Singleton Repository`를 중간 브릿지로 만들어 해결했습니다.
```
[WearRunningService] → repository.updateSensorData()
↓
[RunningViewModel] ← repository.sensorData.collect()
[PostRunViewModel] ← repository.completedResult.collect()
```
```kotlin
@Singleton
class RunningRepository @Inject constructor() {
private val _sensorData = MutableStateFlow(SensorData())
val sensorData: StateFlow<SensorData> = _sensorData.asStateFlow()
private val _completedResult = MutableStateFlow<RunResult?>(null)
val completedResult: StateFlow<RunResult?> = _completedResult.asStateFlow()
fun updateSensorData(data: SensorData) { _sensorData.value = data }
fun updateResult(result: RunResult) { _completedResult.value = result }
fun reset() { _sensorData.value = SensorData(); _completedResult.value = null }
}
```
---
### Step 4. Wear Compose BOM → 명시적 버전으로 변경
Wear Compose는 일반 Compose와 달리 BOM을 제공하지 않습니다.
```kotlin
// 오류 (BOM 미존재)
implementation(platform(libs.androidx.wear.compose.bom))
// 수정
implementation("androidx.wear.compose:compose-material:1.3.1")
implementation("androidx.wear.compose:compose-foundation:1.3.1")
implementation("androidx.wear.compose:compose-navigation:1.3.1")
```
---
### Step 5. gradle.properties 신규 생성
```properties
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
org.gradle.parallel=true
org.gradle.caching=true
```
이 파일이 없으면 `OutOfMemoryError: Java heap space` 및 AndroidX 의존성 오류가 발생합니다.
---
### Step 6. Health Services API 버전 불일치 수정 (15 errors → 0)
`health-services-client:1.1.0-alpha03`에서 인터페이스 이름이 변경되어 15개 에러가 발생했습니다.
```kotlin
// Before — alpha02 이하 API (오류)
import androidx.health.services.client.ExerciseUpdateListener
private val exerciseListener = object : ExerciseUpdateListener {
override fun onExerciseUpdateReceived(update: ExerciseUpdate) { ... }
override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {}
override fun onAvailabilityChanged(dataType: DataType<*, *>, availability: Availability) {}
}
exerciseClient.setUpdateListenerAsync(MoreExecutors.directExecutor(), exerciseListener).await()
// 칼로리 (오류 — AggregateDataType에는 lastOrNull() 없음)
val calorie = metrics.getData(DataType.CALORIES_TOTAL).lastOrNull()?.total?.toInt() ?: 0
```
```kotlin
// After — alpha03+ 정상 API
import androidx.health.services.client.ExerciseUpdateCallback
private val exerciseCallback = object : ExerciseUpdateCallback {
override fun onRegistered() {}
override fun onRegistrationFailed(throwable: Throwable) {}
override fun onExerciseUpdateReceived(update: ExerciseUpdate) { ... }
override fun onLapSummaryReceived(lapSummary: ExerciseLapSummary) {}
override fun onAvailabilityChanged(dataType: DataType<*, *>, availability: Availability) {}
}
exerciseClient.setUpdateCallback(exerciseCallback)
// 칼로리 수정 (단일값 직접 접근)
val calorie = metrics.getData(DataType.CALORIES_TOTAL)?.total?.toInt() ?: 0
```
---
### Step 7. Galaxy Watch Wi-Fi ADB 연결 및 APK 배포
```bash
# 페어링 (최초 1회)
adb pair 192.168.219.108:36911 874867
# 연결
adb connect 192.168.219.108:34371
# 확인
adb devices
```
Android Studio 상단 드롭다운에서 **samsung SM-R935N** 선택 후 ▶ Run
> **[이미지 삽입] Galaxy Watch 5 앱 실행 화면 — 홈 대시보드**
> **[이미지 삽입] Galaxy Watch 5 앱 실행 화면 — 러닝 중 화면**
> **[이미지 삽입] Galaxy Watch 5 앱 실행 화면 — 결과 화면**
---
## 결과와 배운 점
### 배운 점
**1. Wear Compose BOM은 존재하지 않는다**
일반 Compose BOM처럼 `platform(...)` 방식으로 묶으려 하면 빌드 오류 발생. 각 라이브러리에 명시적 버전 지정 필요.
**2. Service는 Hilt ViewModel에 직접 주입 불가**
`@Singleton Repository`를 중간 브릿지로 사용하는 패턴이 정석.
**3. gradle.properties는 수동 프로젝트 설정 시 필수**
자동 생성 시에는 포함되지만, 수동 구성할 때 빠뜨리면 AndroidX 오류 + OOM 발생.
**4. Health Services alpha 버전마다 API가 크게 바뀐다**
`ExerciseUpdateListener` → `ExerciseUpdateCallback`, `setUpdateListenerAsync` → `setUpdateCallback`. alpha 버전 업그레이드 시 반드시 릴리즈 노트 확인 필요.
**5. AggregateDataType vs DeltaDataType 구분**
- `DeltaDataType` (HEART_RATE_BPM, DISTANCE, SPEED): 리스트 → `.lastOrNull()?.value`
- `AggregateDataType` (CALORIES_TOTAL): 단일 누적값 → `?.total`
**6. Wear OS Wi-Fi ADB는 페어링과 연결이 분리되어 있다**
`adb pair`(페어링, 최초 1회)와 `adb connect`(실제 연결)는 포트 번호도 다름.
---
### 시행착오
| 문제 | 원인 | 해결 |
|------|------|------|
| Wear Compose BOM not found | BOM 버전 미존재 | 명시적 버전 1.3.1 지정 |
| OutOfMemoryError | JVM 힙 부족 | gradle.properties -Xmx4096m |
| AndroidX 오류 | gradle.properties 누락 | 파일 신규 생성 |
| Service Hilt 주입 오류 | Service는 Hilt 의존성 불가 | RunningRepository 브릿지 패턴 |
| pacePer km 오타 | 변수명 중간 공백 | pacePerKm으로 수정 |
| ExerciseUpdateListener 미존재 (15 errors) | alpha03에서 API 이름 변경 | ExerciseUpdateCallback으로 교체 |
| lastOrNull() 컴파일 오류 | CALORIES_TOTAL은 단일값 반환 | ?.total 직접 접근 |
| setUpdateListenerAsync 미존재 | alpha03에서 메서드 변경 | setUpdateCallback으로 교체 |
---
### 앞으로의 계획
1. **Samsung Health SDK 연동** — 별도 .aar 다운로드 후 app/libs 추가
2. **폰 ↔ 워치 데이터 동기화 테스트** — 러닝 완료 데이터 Wearable Data Layer 전송 확인
3. **온보딩 화면** — 첫 실행 시 초기 목표 설정 UI
4. **실제 러닝 테스트** — 야외 GPS 환경에서 심박수·거리·페이스 정확도 확인
---
## 도움 받은 글 (옵션)
- [Android Health Services 공식 문서](https://developer.android.com/health-and-fitness/guides/health-services)
- [Wear Compose 공식 샘플 — health-services-walkthrough](https://github.com/android/health-samples)
- [Wearable Data Layer API 가이드](https://developer.android.com/training/wearables/data/messages)
2주차_ "나만의 MVP 앱 구현해 보기"
2
3개의 답글