RunCombi는 반려동물과 함께하는 운동을 기록하고 관리하는 Android 애플리케이션입니다.
- 🐕 반려동물과 함께하는 운동 기록
- 🗺️ GPS 기반 경로 추적
- 👥 사용자 프로필 및 반려동물 관리
| 항목 |
내용 |
버전 |
| 언어 |
Kotlin |
100% |
| 최소 SDK |
Android 8.0 |
API 26 |
| 타겟 SDK |
Android 15 |
API 35 |
| UI 프레임워크 |
Jetpack Compose |
최신 |
| 패턴 |
설명 |
적용 범위 |
| Clean Architecture |
계층별 관심사 분리 |
전체 프로젝트 |
| MVVM |
Model-View-ViewModel 패턴 |
UI 계층 |
| Repository Pattern |
데이터 접근 추상화 |
데이터 계층 |
| UseCase Pattern |
비즈니스 로직 캡슐화 |
도메인 계층 |
| 카테고리 |
라이브러리 |
용도 |
| 의존성 주입 |
Hilt |
DI 컨테이너 |
| 비동기 처리 |
Kotlin Coroutines + Flow |
비동기 작업 |
| 네비게이션 |
Jetpack Navigation Compose |
화면 전환 |
| 상태 관리 |
StateFlow, MutableStateFlow |
UI 상태 |
| 데이터 저장 |
Proto DataStore, Room |
로컬 데이터 |
| 네트워크 |
Retrofit, OkHttp |
API 통신 |
| 이미지 처리 |
Coil |
이미지 로딩 |
| 권한 관리 |
Accompanist Permissions |
권한 처리 |
| 서비스 |
용도 |
통합 방식 |
| Firebase Analytics |
사용자 행동 분석 |
SDK 통합 |
| Firebase Crashlytics |
크래시 리포팅 |
SDK 통합 |
| Google Maps API |
지도 및 위치 서비스 |
API 키 |
| Kakao SDK |
소셜 로그인 |
SDK 통합 |
RunCombi_Android/
├── app/ # 메인 애플리케이션 모듈
├── build-logic/ # 빌드 로직 모듈
├── core/ # 핵심 공통 모듈
│ ├── analytics/ # 분석 도구
│ ├── data/ # 데이터 계층
│ │ ├── auth/ # 인증 데이터
│ │ ├── common/ # 공통 데이터
│ │ ├── history/ # 히스토리 데이터
│ │ ├── setting/ # 설정 데이터
│ │ ├── user/ # 사용자 데이터
│ │ ├── walk/ # 운동 데이터
│ │ ├── datastore/ # 로컬 데이터 저장소
│ │ └── network/ # 네트워크 통신
│ ├── designsystem/ # 디자인 시스템
│ ├── domain/ # 도메인 계층
│ │ ├── auth/ # 인증 도메인
│ │ ├── common/ # 공통 도메인
│ │ ├── history/ # 히스토리 도메인
│ │ ├── setting/ # 설정 도메인
│ │ ├── user/ # 사용자 도메인
│ │ └── walk/ # 운동 도메인
│ ├── navigation/ # 네비게이션
│ └── ui/ # 공통 UI 컴포넌트
└── feature/ # 기능별 모듈
├── history/ # 운동 히스토리
├── login/ # 로그인/인증
├── main/ # 메인 화면
├── setting/ # 설정
├── signup/ # 회원가입
└── walk/ # 운동 추적
프로젝트는 개발 환경과 프로덕션 환경을 분리하기 위해 두 가지 flavor를 제공합니다.
- 목적: 개발 및 테스트 환경
- 특징:
- 가짜 데이터(Mock Data) 사용
- 네트워크 API 호출 없이 로컬에서 테스트
- 빠른 개발 및 디버깅
- 테스트 데이터로 UI 검증
- 목적: 실제 프로덕션 환경
- 특징:
- 실제 서버 API 연동
- Firebase 서비스 연동
- Google Maps API 연동
- 실제 사용자 데이터 처리
app/
├── src/
│ ├── mock/ # Mock flavor 전용 소스
│ │ ├── java/
│ │ │ └── com/combo/runcombi/
│ │ │ └── mock/ # Mock 데이터 구현
│ │ └── res/
│ ├── prod/ # Prod flavor 전용 소스
│ │ ├── java/
│ │ │ └── com/combo/runcombi/
│ │ │ └── prod/ # 실제 서비스 구현
│ │ └── res/
│ └── main/ # 공통 소스
- UI 개발 및 테스트
- 비즈니스 로직 검증
- 네트워크 없이 빠른 반복 개발
- 테스트 데이터로 다양한 시나리오 검증
- 실제 서버와의 연동 테스트
- 성능 및 안정성 검증
- 실제 사용자 시나리오 테스트
- 스토어 배포용 빌드

1. 개발자 코드 Push/PR 생성
↓
2. GitHub Actions 자동 트리거
↓
3. 자동 빌드 실행
↓
4. 빌드 성공 시:
├── Firebase Distribution 자동 업로드
├── Slack #1-android 채널로 알림 전송
└── GitHub Release 자동 생성
↓
5. QA 팀 및 테스터에게 자동 배포
├── Firebase 콘솔에서 APK 다운로드
├── 테스트 환경에서 기능 검증
└── 피드백 수집 및 이슈 등록
@HiltViewModel
class ExampleViewModel @Inject constructor(
private val useCase: ExampleUseCase,
) : ViewModel() {
private val _uiState = MutableStateFlow(ExampleUiState())
val uiState: StateFlow<ExampleUiState> = _uiState.asStateFlow()
private val _eventFlow = MutableSharedFlow<ExampleEvent>()
val eventFlow: SharedFlow<ExampleEvent> = _eventFlow.asSharedFlow()
fun handleAction(action: ExampleAction) {
viewModelScope.launch {
when (action) {
is ExampleAction.Load -> loadData()
is ExampleAction.Submit -> submitData(action.data)
}
}
}
private suspend fun loadData() {
_uiState.update { it.copy(isLoading = true) }
try {
val result = useCase.execute()
_uiState.update {
it.copy(
data = result,
isLoading = false
)
}
} catch (e: Exception) {
_uiState.update {
it.copy(
error = e.message,
isLoading = false
)
}
}
}
}
data class ExampleUiState(
val data: List<ExampleData> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
val selectedItem: ExampleData? = null
)
sealed class ExampleEvent {
object NavigateToDetail : ExampleEvent()
object ShowError : ExampleEvent()
data class ShowToast(val message: String) : ExampleEvent()
}
@Composable
fun ExampleScreen(
onNavigate: (String) -> Unit,
viewModel: ExampleViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.handleAction(ExampleAction.Load)
}
LaunchedEffect(Unit) {
viewModel.eventFlow.collectLatest { event ->
when (event) {
is ExampleEvent.NavigateToDetail -> onNavigate("detail")
is ExampleEvent.ShowError -> { /* 에러 처리 */ }
is ExampleEvent.ShowToast -> { /* 토스트 표시 */ }
}
}
}
ExampleContent(
uiState = uiState,
onAction = viewModel::handleAction
)
}