안드로이드 프레임워크·시스템 학습 노트 목차

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.)

4대 컴포넌트 — Intent·Service·Receiver·ProviderCompose 상태·side-effect — remember·LaunchedEffect