이 가이드에서는 많은 건강/피트니스 앱의 일반적인 기반인 기본 모바일 걸음수 측정기 앱을 빌드하는 방법을 안내합니다.
이 워크플로는 다음 API를 통합합니다.
- SensorManager: 휴대기기에서 걸음 수 데이터를 검색합니다.
- Room: 로컬 데이터 저장소
- 헬스 커넥트: 기기에 건강 및 피트니스 데이터를 저장하고 공유합니다.
데이터 읽기 및 필요한 도구에 관한 추가 지원은 Android 센서 관리자를 사용하여 휴대기기에서 걸음 수 추적하기를 참고하세요.
헬스 커넥트를 사용하기 위한 개발 환경을 아직 설정하지 않았다면 이 시작하기 단계를 따르세요.
휴대기기에서 권한 요청
운동 데이터를 가져오려면 먼저 적절한 권한을 요청하고 부여받아야 합니다.
사용자가 앱을 시작할 때 한 번에 모든 권한을 요청하는 대신 필요한 권한만 요청하고 컨텍스트에 따라 각 권한을 요청하는 것이 좋습니다.
많은 운동 앱에서 사용하는 걸음수 측정기 센서는 ACTIVITY_RECOGNITION
권한을 사용합니다. AndroidManifest.xml 파일에 이 권한을 추가합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
</manifest>
런타임 시 ACTIVITY_RECOGNITION
권한을 요청하려면 권한 요청 문서를 참고하세요.
또한 매니페스트에서 FOREGROUND_SERVICE
를 선언해야 합니다. ACTIVITY_RECOGNITION
권한을 요청하므로 FOREGROUND_SERVICE_TYPE_HEALTH
를 선언합니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"/>
포그라운드 서비스 및 포그라운드 서비스 유형에 관한 자세한 내용은 포그라운드 서비스를 참고하세요.
ViewModel을 사용하여 UI 상태 관리
UI 상태를 올바르게 관리하려면 ViewModel을 사용합니다. Jetpack Compose 및 ViewModel에서 이 워크플로를 더 자세히 살펴볼 수 있습니다.
또한 Compose를 사용하여 UI를 빌드하는 데 중요한 부분이며 단방향 데이터 흐름과 같은 아키텍처 권장사항을 따를 수 있는 UI 레이어링을 사용합니다. UI 레이어링에 관한 자세한 내용은 UI 레이어 문서를 참고하세요.
이 예시 앱에서는 UI에 세 가지 기본 상태가 있습니다.
- 로드 중: 회전하는 원이 표시됩니다.
- 콘텐츠: 오늘의 걸음 수 정보를 표시합니다.
- 오류: 문제가 발생하면 메시지를 표시합니다.
ViewModel
는 이러한 상태를 Kotlin Flow
로 노출합니다. 봉인 클래스를 사용하여 가능한 상태를 나타내는 클래스와 객체를 포함합니다.
class TodayScreenViewModel(...) {
val currentScreenState: MutableStateFlow<TodayScreenState> = MutableStateFlow(Loading)
[...]
}
sealed class TodayScreenState {
data object Loading : TodayScreenState()
data class Content(val steps: Long, val dailyGoal: Long) : TodayScreenState()
data object Error: TodayScreenState()
}
그런 다음 Compose UI는 이 Flow
를 Compose State
로 수집하고 이에 따라 작동합니다.
val state: TodayScreenState = todayScreenViewModel.currentScreenState.collectAsState().value