BookPin은 Kotlin Multiplatform (KMP) + Compose Multiplatform (CMP) 기반의 크로스 플랫폼 앱입니다. Android와 iOS를 동시에 지원하며, MVI 패턴과 Clean Architecture를 적용합니다.
| 구분 | 기술 |
|---|---|
| Language | Kotlin 2.3.0 |
| UI Framework | Compose Multiplatform 1.10.0 |
| Architecture | MVI + Clean Architecture |
| DI | Koin 4.1.1 |
| Network | Ktor 3.4.0 |
| Local Storage | DataStore |
| Image Loading | Coil 3.3.0 |
| Serialization | KotlinX Serialization 1.10.0 |
| Async | KotlinX Coroutines 1.10.2 |
| Logging | Kermit 2.0.8 |
BookPin/
├── android-app/ # Android 진입점
├── iosApp/ # iOS 진입점 (Swift)
├── compose-app/ # 공유 UI 모듈, DI 설정, Navigation
├── feature/ # Feature 모듈
│ ├── auth/ # 인증 Feature 모듈
│ ├── home/ # 홈 Feature 모듈
│ ├── search/ # 검색 Feature 모듈
│ ├── bookmark/ # 책 상세/북마크 Feature 모듈
│ └── settings/ # 설정 Feature 모듈
├── core/ # Core 모듈
│ ├── common/ # 공통 유틸리티 (Snackbar, Extensions)
│ ├── data/ # Data Layer (Repository 구현체)
│ ├── data-api/ # DataSource 인터페이스, DTO
│ ├── data-remote/ # Remote DataSource (Ktor)
│ ├── data-local/ # Local DataSource (DataStore)
│ └── data-auth/ # 소셜 인증 구현체 (Kakao, Apple)
├── designsystem/ # 디자인 시스템 (Color, Typography, Theme)
├── domain/ # Domain Layer (Repository 인터페이스)
├── model/ # 비즈니스 모델
├── build-logic/ # Gradle Convention Plugins
└── gradle/
└── libs.versions.toml
Android 앱 진입점.
BookPinApplication: Koin 초기화MainActivity:BookPinApp()호출
iOS 앱 진입점. Swift에서 getBookPinViewController() 호출.
플랫폼 공통 UI 모듈.
src/
├── commonMain/
│ ├── BookPinApp.kt # 앱 루트 Composable (Scaffold, Snackbar)
│ ├── di/AppModule.kt # Koin 모듈 통합
│ └── navigation/ # Navigator 구현체
├── androidMain/
└── iosMain/
└── KoinHelper.kt # iOS용 Koin 초기화 헬퍼
인증 화면 Feature 모듈.
src/commonMain/kotlin/com/phase/bookpin/auth/
├── AuthScreen.kt # 로그인 UI
├── AuthViewModel.kt # MVI ViewModel
├── AuthState.kt # UI State
├── AuthSideEffect.kt # Side Effects (Snackbar, Navigation)
└── di/AuthModule.kt # Koin 모듈
책 상세/북마크 Feature 모듈.
src/commonMain/kotlin/com/phase/bookpin/bookmark/
├── BookDetailScreen.kt # 책 상세 UI
├── BookDetailViewModel.kt # MVI ViewModel
├── BookDetailState.kt # UI State
├── BookDetailSideEffect.kt # Side Effects
└── di/BookDetailModule.kt # Koin 모듈
공통 유틸리티 모듈.
src/commonMain/kotlin/com/phase/bookpin/common/
├── BaseViewModel.kt # MVI 기반 ViewModel
├── Platform.kt # 플랫폼 구분
├── snackbar/
│ ├── SnackbarHost.kt # Snackbar 인터페이스
│ ├── NoSnackbarHost.kt # No-op 구현체
│ └── SnackbarExtensions.kt # LocalSnackbarHost
└── extensions/
└── FlowExt.kt # collectSideEffect 확장 함수
디자인 시스템 모듈.
Color.kt: BookPinColors, LocalBookPinColorsType.kt: BookPinTypography, LocalBookPinTypographyTheme.kt: BookPinTheme
주요 색상:
- Primary: Gold (#C9A87C)
- Secondary: Teal (#9DBEB7)
- Background: Cream (#FFF8F0)
- Text: Brown (#6B5744)
Repository 인터페이스 및 UseCase.
src/commonMain/kotlin/com/phase/bookpin/domain/
├── auth/AuthRepository.kt # 인증 Repository 인터페이스
└── kakao/KakaoAuth.kt # 카카오 인증 인터페이스
비즈니스 모델.
src/commonMain/kotlin/com/phase/bookpin/model/
└── SocialAuthToken.kt # 소셜 인증 토큰 모델
Repository 구현체 및 DI.
src/commonMain/kotlin/com/phase/bookpin/data/
├── auth/AuthRepositoryImpl.kt # AuthRepository 구현체
└── di/DataModule.kt # Koin 모듈
DataSource 인터페이스 및 DTO.
src/commonMain/kotlin/com/phase/bookpin/data/api/
├── auth/
│ ├── AuthRemoteDataSource.kt # Remote DataSource 인터페이스
│ ├── LoginResponse.kt # 로그인 응답 DTO
│ ├── RefreshTokenRequest.kt
│ ├── RefreshTokenResponse.kt
│ └── SocialAuthTokenRequest.kt
├── datastore/
│ ├── BookPinPreferenceDataStore.kt # Local DataSource 인터페이스
│ └── DataStoreKey.kt
└── navigation/
└── Navigator.kt # Navigation 인터페이스
Remote DataSource 구현체 (Ktor).
src/commonMain/kotlin/com/phase/bookpin/data/remote/
├── auth/AuthRemoteDataSourceImpl.kt # API 호출 구현체
├── client/
│ ├── ClientModule.kt # HttpClient 설정
│ ├── HttpClientExtensions.kt # 응답 처리 확장 함수
│ └── RemoteException.kt # API 에러 클래스
└── di/DataRemoteModule.kt # Koin 모듈
Local DataSource 구현체 (DataStore).
src/
├── commonMain/
│ ├── datastore/
│ │ ├── BookPinPreferenceDataStoreImpl.kt
│ │ └── DataStoreFactory.kt # expect 선언
│ └── di/DataLocalModule.kt
├── androidMain/
│ └── datastore/DataStoreFactory.android.kt
└── iosMain/
└── datastore/DataStoreFactory.ios.kt
소셜 인증 구현체.
src/androidMain/kotlin/com/phase/bookpin/data/auth/kakao/
├── AndroidKakaoAuth.kt # 카카오 로그인 구현체
└── KaKaoLoginState.kt # 로그인 상태 enum
class FeatureViewModel(
private val repository: FeatureRepository
) : BaseViewModel<FeatureState, FeatureSideEffect>() {
override fun createInitialState(): FeatureState = FeatureState()
fun onAction() {
viewModelScope.launch {
reduce { copy(isLoading = true) }
repository.getData()
.onSuccess { data ->
reduce { copy(isLoading = false, data = data) }
}
.onFailure { error ->
reduce { copy(isLoading = false) }
postSideEffect(FeatureSideEffect.ShowSnackbar(error.message))
}
}
}
}@Composable
fun FeatureScreen(viewModel: FeatureViewModel = koinViewModel()) {
val state by viewModel.uiState.collectAsState()
val snackbarHost = LocalSnackbarHost.current
viewModel.sideEffect.collectSideEffect { sideEffect ->
when (sideEffect) {
is FeatureSideEffect.ShowSnackbar -> {
snackbarHost.showSnackbar(sideEffect.message)
}
is FeatureSideEffect.NavigateToHome -> {
// Navigation 처리
}
}
}
// UI 구현
}| Plugin ID | 용도 |
|---|---|
bookpin.android.application |
Android 앱 모듈 설정 |
bookpin.kmp.compose |
KMP + Compose 설정 |
bookpin.ktlint |
KtLint 코드 스타일 검사 |
bookpin.serialization |
KotlinX Serialization |
# Android 빌드
./gradlew :android-app:assembleDebug
# 테스트 실행
./gradlew testDebugUnitTest
# KtLint 검사
./gradlew ktlintCheck
# KtLint 자동 수정
./gradlew ktlintFormat- Screen:
FeatureScreen.kt - ViewModel:
FeatureViewModel.kt - State/SideEffect:
Feature{State|SideEffect}.kt - Repository:
FeatureRepository.kt(인터페이스),FeatureRepositoryImpl.kt(구현체) - DataSource:
FeatureRemoteDataSource.kt(인터페이스),FeatureRemoteDataSourceImpl.kt(구현체)
com.phase.bookpin
├── {feature}/ # Feature 모듈 (auth, home, etc.)
│ ├── FeatureScreen.kt
│ ├── FeatureViewModel.kt
│ ├── FeatureState.kt
│ ├── FeatureSideEffect.kt
│ └── di/FeatureModule.kt
├── domain/{feature}/ # Domain 모듈
│ └── FeatureRepository.kt
├── data/{feature}/ # Data 모듈
│ └── FeatureRepositoryImpl.kt
└── data/api/{feature}/ # Data-API 모듈
├── FeatureRemoteDataSource.kt
└── FeatureDto.kt
model/모듈의 도메인 모델(data class)에는 디폴트 파라미터 값을 사용하지 않는다- DTO(
core/data-api)에서 매퍼를 통해 변환할 때 모든 값을 명시적으로 전달한다
- Wildcard import 허용
- @Composable 함수 대문자 시작 허용
- 비활성화된 규칙:
no-wildcard-imports,filename,function-signature
@Composable
fun MyScreen() {
BookPinTheme {
Text(
text = "Hello",
style = BookPinTheme.typography.bodyLarge,
color = BookPinTheme.colors.primary
)
}
}- Main Branch:
develop - Feature Branch:
feature/#이슈번호또는feature/#이슈번호-기능명 - Commit Message:
#이슈번호 : 작업내용
GitHub Actions (ci.yml):
assembleDebug- 빌드 검증testDebugUnitTest- 유닛 테스트ktlintCheck- 코드 스타일 검사
| 구분 | 경로 |
|---|---|
| Android 진입점 | android-app/src/main/java/.../MainActivity.kt |
| Android Application | android-app/src/main/java/.../BookPinApplication.kt |
| iOS 진입점 | compose-app/src/iosMain/.../getBookPinViewController.kt |
| iOS Koin Helper | compose-app/src/iosMain/.../KoinHelper.kt |
| 공통 App | compose-app/src/commonMain/.../BookPinApp.kt |
| App DI Module | compose-app/src/commonMain/.../di/AppModule.kt |
| BaseViewModel | core/common/src/commonMain/.../BaseViewModel.kt |
| Snackbar | core/common/src/commonMain/.../snackbar/ |
| 테마 | designsystem/src/commonMain/.../Theme.kt |
| 버전 카탈로그 | gradle/libs.versions.toml |
- Compile SDK: 36
- Min SDK: 26
- Target SDK: 36
- JVM Target: 17