반응형
🩺 Android | Health Connect 걸음 수 집계 캐시 & 상단바 최소 높이 적용

개요 (Intro)
- 오늘의 목표: 만보계 핵심 로직(걸음 수 집계/캐시) 안정화 + 메인 화면 상단바 UI 컴팩트화
- 배경: 기존 raw StepsRecord 합산 방식은 성능/정확도 측면 한계. TopAppBar 기본 높이 과도.
- 해결하려는 문제: 중복 데이터 합산 리스크, 빈번한 집계 호출로 인한 UI 지연, 화면 상단 낭비 공간
- 사용 기술: Kotlin, Jetpack Compose, Health Connect, MVVM, Coroutine, Flow
📅 날짜: 2025.11.18
🎯 목표: Health Connect 걸음 수 Aggregate + 캐시 적용 & 상단바 높이 24dp로 축소
🧰 기술: Kotlin, Android Studio, Compose, Health Connect, MVVM, Coroutines, Flow
문제 정의 (Problem / Motivation)
- 걸음 수 계산을 raw StepsRecord 반복 합산 → 중복/성능 저하 가능성.
- 집계를 화면 갱신마다 수행 → 불필요한 I/O 증가.
- TopAppBar 기본 높이로 인해 실제 콘텐츠 표시 영역 감소.
- DistanceRecord 읽기 함수에서
plus()결과 미반영 버그 존재(기존 리스트 누적 실패).
발견된 버그 및 개선 필요 코드:
// 기존 (버그): distance 누적 실패
val rValue : List<DistanceRecord> = emptyList()
for (record in response.records) {
rValue.plus(record) // 결과를 재할당하지 않아 누락됨
}
// 기존: Steps 합산 (중복 가능성 & 비효율)
val response = client.readRecords(ReadRecordsRequest(StepsRecord::class, ...))
var total = 0L
for (r in response.records) total += r.count
해결 과정 (How I Solved It)
- DistanceRecord 누락 수정:
MutableList로 변환 후add()로 확정 저장. - Aggregate API 도입:
StepsRecord.COUNT_TOTAL메트릭 사용. - 캐시 레이어 추가: 30초 TTL + 자정 경계 무효화.
getTodaySteps(forceRefresh)제공. - 세션 종료 시간 매핑 수정:
endTime잘못된 startTime 재사용 → 실제 endTime 적용. - UI 축소: TopAppBar 제거 → 24dp Box +
statusBarsPadding()로 최소 높이 상단바. - CombinedUiState 도입: 여러 StateFlow를 하나로 합쳐 recomposition 감소.
핵심 적용 코드:
// Distance 누락 수정
val recordsAccum = mutableListOf<DistanceRecord>()
for (record in response.records) {
recordsAccum.add(record)
}
// Aggregate + 캐시 (HealthConnectManager)
private data class StepCountCache(val dayEpoch: Long, val timestampMs: Long, val value: Long)
private var stepCountCache: StepCountCache? = null
private val STEP_CACHE_TTL_MS = 30_000L
private suspend fun computeTodayStepsAggregate(): Long {
val startZdt = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
val endZdt = startZdt.plusDays(1)
val agg = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startZdt.toInstant(), endZdt.toInstant())
)
)
return agg[StepsRecord.COUNT_TOTAL] ?: 0L
}
suspend fun getTodaySteps(forceRefresh: Boolean = false): Long {
val now = System.currentTimeMillis()
val todayEpoch = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toInstant().epochSecond
val cached = stepCountCache
val valid = cached != null && !forceRefresh &&
cached.dayEpoch == todayEpoch && (now - cached.timestampMs) < STEP_CACHE_TTL_MS
if (valid) return cached.value
val fresh = computeTodayStepsAggregate()
stepCountCache = StepCountCache(todayEpoch, now, fresh)
return fresh
}
// ViewModel 사용
_stepsTotal.value = healthConnectManager.getTodaySteps(forceRefresh = false)
// 상단바 최소 높이 UI
Box(
modifier = Modifier
.fillMaxWidth()
.height(24.dp) // status bar height target
.background(Color(0xFF5E82FC))
.statusBarsPadding()
) { /* Icon + Title */ }
결과 (Result)
- 걸음 수 집계 호출 빈도 ↓ (30초 캐시로 반복 쿼리 방지).
- UI 상단 높이 축소로 세로 가시 영역 증가.
- 세션 종료 시간 표시 정확도 개선.
- Distance 데이터 정상 누적 (차트/리스트 값 일치).
✅ Aggregate 기반 일일 걸음 수 정확도 상승
⚡ 집계 호출 체감 대기시간 단축 & 불필요 로딩 감소
🖼 상단바 24dp 적용으로 콘텐츠 노출 영역 확대
느낀 점 / 회고 (Reflection)
- Health Connect Aggregate API를 우선적으로 사용하는 설계가 유지보수성과 정확성을 동시에 확보.
- 캐시 TTL 결정(30초)은 사용자 즉각 반응성과 자원 절약 타협점으로 적절.
- UI 영역은 초기 템플릿 의존보다 실제 사용 시나리오 측정 후 최소화하는 것이 좋음.
- 다음 개선: WorkManager로 백그라운드 자동 재동기화, 캐시 만료 시 알림형 업데이트.
참고자료 (References)
다음 로그 예정: 백그라운드 differential changes token 주기 처리 + Room 캐시 Layer 도입.
반응형
'모바일 앱(안드로이드)' 카테고리의 다른 글
| 🛠 Android | Coupang API + Hilt DI + AdsScreen UX/포맷 개선 작업 기록 (1) | 2025.11.25 |
|---|---|
| 🦾 Android | 하단 바 + Navigation Compose + 보안 키 주입(ResValue) 적용기 (1) | 2025.11.23 |
| 🪄 Wear OS | Complication 탭 액션 구현 및 타일/칩 UI 정리 (1) | 2025.11.19 |
| 🦾 Android | 서식 캔버스 폼 - 이 앱 개발의 기본 지식 정리 (2) | 2025.11.17 |
| 개발일기: Wear OS Complication 클릭 시 앱 실행하기 (1) | 2025.11.15 |