안드로이드 아키텍처 용어 사전
MVVMUDF

단방향 데이터 흐름

상태는 아래로, 이벤트는 위로만 흐르게 하는 규칙. 상태 변경 경로를 하나로 만든다.

상태는 아래로, 이벤트는 위로만 흐르게 하는 규칙.

  Repository ──데이터──→ ViewModel ──상태──→ UI
       ↑                      ↑
       └────── 호출 ──────────┴──── 이벤트 ──── 사용자

한 바퀴 돈다. 역주행이 없다

양방향이면 무엇이 문제인가

양방향 바인딩 (예: XML 의 two-way binding)

  • 화면이 값을 직접 고치고, 모델도 값을 고친다

  • "이 값이 왜 3이 됐지?" 를 추적할 수 없다

    • 화면이 바꿨나, ViewModel 이 바꿨나, 다른 관찰자가 바꿨나
  • 순환 갱신이 생긴다 (A 가 B 를 바꾸고 B 가 다시 A 를 바꾼다)

단방향의 실제 코드

// ViewModel — 밖으로는 읽기 전용만 노출한다
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

fun onQueryChange(q: String) { _uiState.update { it.copy(query = q) } }

// UI — 상태를 읽고 이벤트만 올린다
@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    SearchBar(text = state.query, onTextChange = viewModel::onQueryChange)
}

MutableStateFlow 를 private 으로 두는 것이 핵심이다

  • 밖에 노출하면 아무나 state.value = ... 로 고칠 수 있다
  • 단방향이 깨진다

collectAsStateWithLifecycle을 쓰는 이유

  • collectAsState() — 화면이 백그라운드로 가도 계속 수집한다

  • collectAsStateWithLifecycle() — STARTED 아래로 내려가면 수집을 멈춘다

  • 안 멈추면 보이지도 않는 화면을 위해 계속 데이터를 받고 다시 그린다

  • 배터리와 네트워크 낭비

텍스트 필드에서 생기는 함정

입력 → 이벤트 → ViewModel → 상태 → 다시 그리기 한 바퀴가 도는 동안 지연이 있으면 글자가 밀린다

대응

  • 로컬 remember 로 텍스트를 두고 debounce 후에만 ViewModel 로 올린다
  • 또는 Compose 의 TextFieldState 를 쓴다

면접 함정

  • "UDF는 Compose 전용" → View 시스템에서도 동일하게 적용한다. 패러다임이지 API가 아니다.
  • "UDF면 성능이 좋다" → 얻는 것은 예측 가능성이다. 성능은 별개 축이다.

액션을 하나의 타입으로 모으기 (MVI)

sealed interface HomeIntent {
    data class QueryChanged(val q: String) : HomeIntent
    data object Refresh : HomeIntent
    data class ItemClicked(val id: Long) : HomeIntent
}

fun onIntent(intent: HomeIntent) = when (intent) {
    is HomeIntent.QueryChanged -> _uiState.update { it.copy(query = intent.q) }
    HomeIntent.Refresh -> refresh()
    is HomeIntent.ItemClicked -> navigateTo(intent.id)
}
  • 장점 — 화면에 들어오는 모든 입력이 한 타입으로 모인다 → 로깅·재현이 쉽다

    • when 이 exhaustive 라 새 액션을 빠뜨릴 수 없다
  • 단점 — 콜백 하나 추가하는 데 타입 선언이 늘어난다

  • MVVM 이 UDF 를 '권장' 한다면 MVI 는 '강제' 한다 — 정도의 차이다

상태가 되돌아 오르는 흔한 누수

// ❌ UI 가 ViewModel 의 상태를 직접 고친다
viewModel.uiState.value = viewModel.uiState.value.copy(query = "x")

// ❌ Composable 이 상태를 들고 ViewModel 도 들고 있어 둘이 어긋난다
var localQuery by remember { mutableStateOf(state.query) }   // 동기화 지점이 둘

둘 다 '단일 출처' 를 깬 것이다 증상: 값이 튀거나, 갱신이 한 박자 늦거나, 회전 후 옛 값이 남는다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — MVVM·단방향 데이터 흐름(UDF)