android-patterns

Provides Android coding patterns for Kotlin 2.0 and Jetpack Compose projects. Includes MVI architecture, Clean Architecture layers, Compose best practices, and Coroutines/Flow patterns. Use when writing Android code, implementing features, reviewing code, or learning patterns. Use when user mentions: MVI 패턴, 클린 아키텍처, 컴포즈 패턴, 코루틴, Flow, 상태 관리, 아키텍처, ViewModel 패턴, 레이어 구조, 패턴 설명.

$ Installer

git clone https://github.com/Enso-Soft/lotto-assist /tmp/lotto-assist && cp -r /tmp/lotto-assist/.claude/skills/android-patterns ~/.claude/skills/lotto-assist

// tip: Run this command in your terminal to install the skill


name: android-patterns description: | Provides Android coding patterns for Kotlin 2.0 and Jetpack Compose projects. Includes MVI architecture, Clean Architecture layers, Compose best practices, and Coroutines/Flow patterns. Use when writing Android code, implementing features, reviewing code, or learning patterns. Use when user mentions: MVI 패턴, 클린 아키텍처, 컴포즈 패턴, 코루틴, Flow, 상태 관리, 아키텍처, ViewModel 패턴, 레이어 구조, 패턴 설명.

Android Coding Patterns

Kotlin 2.0 + Jetpack Compose 프로젝트를 위한 코딩 패턴 가이드입니다.

Architecture: Clean Architecture + MVI

Layer Overview

LayerResponsibilityDependencies
DomainBusiness logic, UseCasesNone (pure Kotlin)
DataRepository impl, DataSourcesDomain
PresentationViewModel, UI State, ComposeDomain
┌─────────────────────────────────────────┐
│            Presentation                 │
│  (ViewModel, Compose UI, Contract)      │
└─────────────────┬───────────────────────┘
                  │ depends on
┌─────────────────▼───────────────────────┐
│              Domain                     │
│  (UseCases, Models, Repository I/F)     │
└─────────────────┬───────────────────────┘
                  │ depends on
┌─────────────────▼───────────────────────┐
│               Data                      │
│  (Repository Impl, DataSources, DTOs)   │
└─────────────────────────────────────────┘

Quick References

MVI Pattern

State, Event, Effect로 구성된 단방향 데이터 흐름 패턴.

// Contract 기본 구조
data class UiState(val isLoading: Boolean = false, val data: List<Item> = emptyList())
sealed interface Event { data class LoadData(val id: String) : Event }
sealed interface Effect { data class ShowToast(val message: String) : Effect }

상세 가이드: patterns/mvi-pattern.md

Compose Patterns

상태 호이스팅, Modifier 규칙, Recomposition 최적화.

// State Hoisting 기본
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) { Text("$count") }
}

상세 가이드: patterns/compose-patterns.md

Clean Architecture

레이어별 구현 가이드 및 의존성 규칙.

// UseCase 기본 구조
class GetLottoResultUseCase @Inject constructor(
    private val repository: LottoRepository
) {
    suspend operator fun invoke(roundNumber: Int): Result<LottoResult> =
        repository.getLottoResult(roundNumber)
}

상세 가이드: patterns/clean-architecture.md

Coroutines & Flow

비동기 처리 및 반응형 스트림 패턴.

// StateFlow 기본
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

상세 가이드: patterns/coroutines-flow.md

Code Templates

ViewModel Template

@HiltViewModel
class FeatureViewModel @Inject constructor(
    private val useCase: SomeUseCase
) : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    private val _effect = Channel<Effect>()
    val effect = _effect.receiveAsFlow()

    fun onEvent(event: Event) {
        when (event) {
            is Event.Load -> loadData()
        }
    }

    private fun loadData() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            useCase().fold(
                onSuccess = { data -> _uiState.update { it.copy(isLoading = false, data = data) } },
                onFailure = { _effect.send(Effect.ShowError(it.message)) }
            )
        }
    }
}

UseCase Template

class GetDataUseCase @Inject constructor(
    private val repository: DataRepository
) {
    suspend operator fun invoke(param: String): Result<Data> =
        repository.getData(param)
}

Repository Template

// Domain layer - Interface
interface DataRepository {
    suspend fun getData(param: String): Result<Data>
}

// Data layer - Implementation
class DataRepositoryImpl @Inject constructor(
    private val remoteDataSource: RemoteDataSource,
    private val localDataSource: LocalDataSource
) : DataRepository {
    override suspend fun getData(param: String): Result<Data> =
        runCatching { remoteDataSource.fetchData(param).toDomain() }
}

Module Structure

app/                    # Application entry
core/
├── domain/            # Pure Kotlin
├── data/              # Repository impl
├── network/           # Retrofit
├── database/          # Room
└── di/                # Hilt modules
feature/
├── home/              # Feature module
└── qrscan/            # Feature module