Jetpack Compose 기초 — 선언형 UI·recomposition
Jetpack Compose는 안드로이드의 현대 선언형 UI 툴킷이다. 기존 명령형 View 시스템(XML + findViewById + 수동 갱신)과 달리 — UI를 상태의 함수로 선언한다. UI = f(state): 상태가 바뀌면 Compose가 영향받는 부분만 다시 그린다(recomposition). *"어떻게 바꿀지"가 아니라 "어떤 모습이어야 하는지"*만 적는다. (출처: Android — Compose mental model / Thinking in Compose.)
선언형 — UI는 상태의 함수
명령형 View에선 데이터가 바뀔 때마다 textView.text = ...로 직접 갱신했다. Compose에선 상태를 읽는 컴포저블을 선언만 하면 — 상태가 바뀔 때 그 컴포저블이 자동으로 다시 호출되어 새 UI를 만든다.
@Composable
fun Greeting(name: String) { // @Composable = UI를 그리는 함수
Column(Modifier.padding(16.dp)) {
Text(text = "안녕, $name", style = MaterialTheme.typography.titleLarge)
Text(text = "Compose")
}
}
@Composable 함수는 값을 반환하지 않고 UI를 방출한다. Column/Row/Box로 배치하고, Text/Button/Image 등으로 내용을 그리며, Modifier로 크기·여백·클릭 등을 체이닝한다.
상태와 recomposition
상태는 **remember { mutableStateOf(...) }**로 만든다 — remember는 recomposition 사이에 값을 보존하고, *mutableStateOf는 값이 바뀌면 그 값을 읽는 컴포저블을 다시 호출(recompose)*한다.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // 상태
Button(onClick = { count++ }) { // 상태 변경
Text("클릭 $count") // 상태 읽음 → 바뀌면 이 부분 recompose
}
}
핵심 — 상태를 읽는 곳만 recompose된다. Compose는 바뀌지 않은 부분을 건너뛰어(skippable) 효율적으로 갱신한다.
recomposition의 규칙 — 컴포저블은 순수해야
recomposition을 안전하게 하려면 컴포저블이 지켜야 할 규칙이 있다.
- 부수효과(side-effect) 없이 순수해야 — 같은 입력이면 같은 UI. 컴포저블 본문에서 변수 수정·네트워크 호출·로깅 같은 부수효과를 직접 하면 안 된다(recomposition마다 반복 실행됨). → side-effect API([04]).
- 순서·횟수를 가정하지 마라 — Compose는 언제·몇 번·어느 순서로 recompose할지 보장하지 않고, 병렬·건너뜀도 한다.
- 빠르게 — 컴포저블은 자주 호출되니 무거운 계산은
remember로 캐시한다.
// ❌ 컴포저블 본문에서 부수효과 — recompose마다 실행됨
@Composable fun Bad(items: List<Int>) {
val sorted = items.sortedDescending() // 매 recompose마다 정렬 (낭비)
log("rendered") // 매번 로깅 (부수효과)
}
// ✅ remember로 캐시
@Composable fun Good(items: List<Int>) {
val sorted = remember(items) { items.sortedDescending() } // items 바뀔 때만
}
Modifier — 꾸밈·동작을 체이닝
**Modifier**는 컴포저블의 크기·여백·배경·클릭·정렬 등을 순서대로 체이닝한다. 순서가 결과를 바꾼다(padding 후 background와 background 후 padding이 다름).
Text("탭",
Modifier
.padding(8.dp)
.background(Color.LightGray)
.clickable { onTap() }
.fillMaxWidth())
상태 호이스팅과 UDF로 연결
[andarch 07]에서 봤듯 — 상태를 컴포저블 밖(ViewModel/상위)으로 끌어올리고(stateless), 상태는 내려주고 이벤트는 콜백으로 올린다. Compose는 이 단방향 흐름과 천생연분이라, ViewModel의 StateFlow를 collectAsStateWithLifecycle()로 구독해 화면을 그린다.
정리 — 상태를 선언하면 UI가 따라온다
Compose의 핵심 — UI = f(state) 선언형, 상태(remember+mutableStateOf)가 바뀌면 그걸 읽는 부분만 recompose, 컴포저블은 순수해야(부수효과는 side-effect API로), Modifier로 꾸밈·동작 체이닝(순서 중요). "어떻게 바꿀지"를 신경 쓰던 명령형에서 "어떤 모습인지만 선언"하는 사고로의 전환이 Compose의 전부다. 다음 장의 상태·side-effect가 이를 안전하게 다루는 도구다.
한 줄 요약 — Compose=선언형
UI=f(state).@Composable은 UI 방출(반환 X). 상태는remember{mutableStateOf()}, 바뀌면 읽는 부분만 recompose(skippable). 컴포저블은 순수(부수효과 금지·순서/횟수 가정 금지·무거운 건 remember).Modifier체이닝(순서 중요). ViewModel StateFlow는collectAsStateWithLifecycle.
(출처: Android — Thinking in Compose / State / Modifiers / Compose State.)