안드로이드 아키텍처 학습 노트 목차

MVVM·단방향 데이터 흐름(UDF)

UI 계층의 표준 패턴이 **MVVM(Model-View-ViewModel)**이고, 그 데이터가 흐르는 규칙이 **UDF(단방향 데이터 흐름)**다. 핵심 한 줄 — ViewModel이 화면 상태를 들고, 화면은 그 상태를 그리며(상태↓), 사용자 동작은 이벤트로 올려보낸다(이벤트↑). 이 단방향성이 상태를 예측 가능하게 하고 화면 회전·재구성에도 안정적으로 만든다. (출처: Android — UI layer / UI events / [Now in Android].)

MVVM — 세 역할

  • Model — 데이터·비즈니스(데이터 계층). 화면을 모른다.
  • View화면(Compose/Activity/Fragment). 상태를 그리고 이벤트를 위임할 뿐, 로직을 갖지 않는다(수동적).
  • ViewModel화면 상태를 보유하고 이벤트를 받아 상태를 갱신한다. 화면(View)을 참조하지 않는다(그래서 회전에도 살아남고 테스트 가능).
다이어그램 로딩 중…

ViewModel — 화면보다 오래 산다

안드로이드 ViewModel(Jetpack)은 화면(Activity/Fragment)보다 긴 생명주기를 갖는다 — 화면 회전으로 Activity가 재생성돼도 ViewModel은 유지되어 상태가 날아가지 않는다. 그래서 "화면 상태와 그 상태를 만드는 로직"을 ViewModel에 둔다.

class ListViewModel(private val repo: MemberRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()   // 상태를 읽기 전용으로 노출

    fun onRefresh() = viewModelScope.launch {                   // 이벤트 처리
        _uiState.value = UiState.Loading
        runCatching { repo.load() }
            .onSuccess { _uiState.value = UiState.Success(it) }
            .onFailure { _uiState.value = UiState.Error(it.message ?: "오류") }
    }
}

viewModelScope(코루틴 스코프)는 ViewModel이 소멸되면 자동 취소([07 코루틴])라 누수가 없다.

UDF — 상태는 아래로, 이벤트는 위로

단방향 데이터 흐름의 두 방향:

  • 상태(state)는 아래로(↓) — ViewModel의 StateFlow를 화면이 구독해 그린다. 화면은 상태의 함수같은 상태면 같은 화면.
  • 이벤트(event)는 위로(↑) — 클릭·입력 같은 사용자 동작을 화면이 ViewModel의 함수 호출로 올려보내고, ViewModel이 상태를 갱신한다.
@Composable
fun ListScreen(viewModel: ListViewModel) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()  // 상태 ↓ 구독
    when (state) {
        is UiState.Loading -> Spinner()
        is UiState.Success -> MemberList(
            members = state.data,
            onRefresh = viewModel::onRefresh                       // 이벤트 ↑ 위임
        )
        is UiState.Error -> ErrorView(state.message)
    }
}

화면은 절대 상태를 직접 바꾸지 않는다 — 항상 이벤트를 ViewModel에 올려 ViewModel이 새 상태를 만들어 내려보낸다. 이 규칙이 "상태가 어디서 바뀌는지"를 ViewModel 한 곳으로 모아 디버깅·테스트를 쉽게 한다.

UI 상태는 불변 객체로

UI 상태는 *불변(immutable)*으로 모델링한다 — 보통 data classsealed([03 클래스]). 갱신은 copy로 새 상태를 만든다. 부분 변경을 직접 하지 않으니 경쟁 조건·예측 불가가 줄어든다.

data class ScreenState(
    val isLoading: Boolean = false,
    val items: List<Member> = emptyList(),
    val error: String? = null
)
_state.update { it.copy(isLoading = true) }   // 불변 갱신

일회성 이벤트는 SharedFlow로

토스트·화면 이동 같은 일회성 이벤트를 상태(StateFlow)로 표현하면 회전 후 재실행 같은 버그가 난다. 이런 건 SharedFlow([08 Flow])나 이벤트 래퍼로 한 번만 소비되게 한다.

정리 — 단방향이 예측 가능성을 만든다

MVVM+UDF의 핵심 — ViewModel이 화면 상태를 보유(회전에도 생존), 상태는 아래로(StateFlow 구독)·이벤트는 위로(함수 호출), 상태는 불변(copy 갱신), 일회성 이벤트는 SharedFlow. 화면을 상태의 함수로 만들면 — 같은 상태면 같은 화면이라 추론·테스트·재구성이 쉬워진다. 이게 Compose 선언형 UI와 천생연분인 이유다.

한 줄 요약 — MVVM: View(수동적 그리기)·ViewModel(상태 보유·화면 모름·회전 생존)·Model(데이터). UDF: 상태↓(StateFlow)·이벤트↑(함수 호출), 화면은 상태의 함수. 상태는 불변 data class+copy, 일회성은 SharedFlow. viewModelScope로 누수 없는 비동기.

(출처: Android — UI layer architecture / ViewModel / UI events / Now in Android.)

앱 아키텍처 개요 — 관심사 분리·권장 계층Clean Architecture — domain·data·presentation