훌륭한 개발자가 되기 위하여
[Jetpack Compose] State Hoisting이란? 본문
State Hoisting이란?
Compose에서 상태 호이스팅은 "컴포저블이 직접 상태를 가지지 않고, 호출자가 상태를 소유하도록 상태를 끌어올리는 패턴"을 말한다. 일반적으로 value: T와 onValueChange: (T) -> Unit 두 파라미터로 상태를 표현하고, 실제 상태는 부모 컴포저블이나 ViewModel이 가진다.
상태 호이스팅은 근본적으로 단방향 데이터 흐름 원칙을 따른다.
즉, 상태는 위에서 아래로 내려가고, 이벤트는 아래에서 위로 올라가면서 UI를 더 쉽게 관리하고 확장 가능하게 만든다.

상태 호이스팅 특성
- 상태(State)는 부모 컴포저블에서 관리한다.
- 이벤트(Event) 또는 트리거(onClick, onValueChange)는 자식에서 발생하고, 콜백을 통해 부모로 전달되어 상태를 업데이트한다.
- Compose에서 상태 호이스팅의 전형적인 형태
- value: T - 표시할 상태값
- onValuleChange: (T) -> Unit - 상태 변경을 부모에 요청하는 콜백
Stateless vs StateFul
Stateless 예제 (상태 호이스팅 적용)
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
}
}
위 코드에서는 name과 onNameChange를 HelloContent의 매개변수로 전달하고, 상태는 상위 컴포저블인 HelloScreen에서 관리한다. 이로 인해 HelloContent는 어떤 상태도 관리하지 않는 Stateless 컴포저블이 되며, 여러 화면에서 동일한 UI를 재사용하고 테스트하기가 훨씬 쉬워진다.
상태 저장 위치를 ViewModel, 상위 화면, 다른 State holder 등으로 옮기더라도 HelloContent의 구현은 바꿀 필요가 없다.
StateFul 예제 (내부에서 상태 관리)
@Composable
fun HelloScreen() {
HelloContent()
}
@Composable
fun HelloContent() {
val (name, onNameChange) = remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
}
}
이 코드에서는 HelloContent가 내부적으로 상태를 직접 관리한다. 호출자는 별다른 매개변수를 넘기지 않아 편하지만, HelloContent의 상태를 외부에서 관찰, 제어하거나 다른 컴포저블과 공유하기가 어렵기 때문에 유연성이 감소할 수 밖에 없다.
HelloContent가 내부적으로 상태를 관리하므로 외부에서 동작을 제어하거나 커스텀하기가 더 어려워지기 때문에 재사용하기가 더 어려워질 수 있다.
상태 호이스팅의 장점
- 재사용성
상태와 이벤트 콜백을 분리하면, 특정 화면이나 ViewModel 구현에 묶이지 않고 재사용 가능한 UI 컴포넌트를 만들 수 있다.
- 테스트 단순화
Stateless 컴포저블은 "입력 -> 출력"만 보면 되기 때문에, 다양한 상태값을 주입해서 시나리오 테스트를 작성하기 쉽다.
- 관심사 분리
상태 관리와 비즈니스 로직은 ViewModel, State Holder에서 담당하고 컴포저블은 UI렌더링에만 집중할 수 있다.
- 단방향 데이터 흐름 지원
상태는 부모에서 자식으로만 내려가고, 이벤트는 자식에서 부모로만 올라오므로, 데이터가 어디서 변경되는지 추적하기 쉬워진다.
- 향상된 상태 관리
상태 호이스팅을 사용하면 ViewModel 또는 부모 컴포저블과 같은 상위 수준 컨테이너에서 상태를 중앙 집중화할 수 있다.
항상 State Hoisting을 해야 할까?
Android 공식 문서에서도 “다른 컴포저블이 제어할 필요가 없는, 단순한 UI 로컬 상태”는 컴포저블 내부에 둘 수 있다고 설명한다.
@Composable
fun ChatBubble(
message: Message
) {
var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state
Text(
text = AnnotatedString(message.content),
modifier = Modifier.clickable {
showDetails = !showDetails // Apply UI logic
}
)
if (showDetails) {
Text(message.timestamp)
}
}
이 예제에서 showDetails는 버블 안에서만 읽고 수정되고, UI 로직도 클릭 시 펼치기/접기 정도로 단순하다. 다른 컴포저블이 이 상태를 제어하거나 공유할 필요가 없기 때문에, 굳이 상태를 끌어올려도 얻을 수 있는 이점이 크지 않다.
즉, 상태와 상태에 적용하는 로직이 단순하고 UI 계층 구조의 다른 부분에서 상태가 필요하지 않은 경우에 유용한 방식이다.
참고자료
https://developer.android.com/develop/ui/compose/state?hl=ko&_gl=1*1ftizxu*_up*MQ..*_ga*NjE3OTY2NTQxLjE3NzMyMDcyNDI.*_ga_6HH9YJMN9M*czE3NzMyMDcyNDEkbzEkZzAkdDE3NzMyMDcyNDEkajYwJGwwJGg5MjgxODk3Ng..zEkZzAkdDE3NzMyMDcyNDEkajYwJGwwJGg5MjgxODk3Ng..
상태 및 Jetpack Compose | Android Developers
이 문서에서는 Android 앱의 상태, remember 및 mutableStateOf와 같은 API를 사용하여 Jetpack Compose에서 상태를 관리하는 방법, 상태 호이스팅이 컴포저블 재사용성을 개선하는 방법, rememberSaveable을 사용하
developer.android.com
https://developer.android.com/develop/ui/compose/state-hoisting?hl=ko
상태를 호이스팅할 대상 위치 | Jetpack Compose | Android Developers
이 문서에서는 Jetpack Compose 애플리케이션에서 UI 상태를 호이스팅하기 위한 권장사항을 설명하고, 상태 관리가 UI 로직과 비즈니스 로직 중 어느 쪽에서 주도되는지 시나리오를 자세히 설명합니
developer.android.com
'안드로이드' 카테고리의 다른 글
| Jetpack Compose에서 Paging3로 대용량 데이터 처리하기 (0) | 2025.09.18 |
|---|---|
| [JetpackCompose] Compose Side Effect (0) | 2025.08.14 |
| [Jetpack Compose] State와 Remember란? (0) | 2025.08.06 |
| Jetpack Compose의 안정성(Stability) (0) | 2025.07.27 |
| Jetpack Compose의 동작 구조 (0) | 2025.07.26 |