
기존 XML 방식에는 큰 문제점이 하나 있었는데, 그것은 바로 View 가 스스로의 상태를 정의하고 보존한다는 것입니다. 상태는 View 를 그 자체로 인식할 수 없도록 하며, 이는 곧 테스트 가능성과 재사용성을 저해하는 원인이 됩니다.
State Hoisting
Jetpack Compose 를 활용하여 UI 를 작성할 때에 사용할 수 있는 패턴입니다. Composable 은 XML 방식의 View 와 다르게, 그 자체로는 Stateless 상태입니다. 얼마든 재사용할 수 있고, 얼마든 테스트할 수 있습니다.
다만, Composable 이 특정한 상태가 부여되거나 특정 상황에 결속되는 경우가 있습니다. 전자는 TextField, Scrollable 한 Composable 이 해당하고, 후자의 경우 클릭 이벤트를 처리하는 코드가 포함된 Composable 등 입니다.
해당 Composable 들의 경우, 클릭에 대한 액션이나 스크롤, 또는 값 저장(TextField) 로직을 Composable 내부에 두게 되면 해당 Composable 과 상태는 서로에 대해 상호 의존성을 갖게되며, 테스트가 불가하고 재사용할 수 없게 됩니다.
또한, State 와 관련한 코드가 경우에 따라 다른 객체 등을 참조할 수가 있는데, 그러한 경우 당연스럽게도 재사용할 수 없고, 테스트시에도 별도의 Mock 객체를 생성해야하는 등의 수고가 뒤따릅니다.
이러한 문제를 해결하기 위해 State Hoisting 을 사용할 수 있습니다.
왜 Hoisting 이란 용어를 썼는가에 대한 짐작을 해보자면, Hoisting 은 끌어 올리다, 견인하다 라는 뜻입니다.

Stateful 하게 만들어질 수 있는 Composable 의 State 를 상위 수준에서 해결할 수 있도록 파라미터를 설정하는 방식입니다. 이렇게 하면 해당 Composable 을 호출하는 상위 스코프에서 Composable 의 상태를 설정하거나 이벤트를 관리할 수 있습니다.
파라미터를 넘기는 것은 크게 다룰 만한 내용이 없어 서술하지 않습니다. 중요한 건 이벤트인데요, 이벤트를 다루는 State Hoisting Pattern 의 구현을 위해, 우리는 람다를 사용할 수 있습니다. 즉, Composable 파라미터에 함수 파라미터를 사용하는 것입니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ExampleComposeTheme {
ExampleComposable { // onClick() 람다
// do something.
}
}
}
}
}
@Composable
fun ExampleComposable(onClick: () -> Unit) {
Text("Example.", modifier = Modifier
.width(300.dp)
.height(200.dp)
.clickable {
onClick()
})
}
이와 같이 구현하면 해당 Composable 은 onClick() 함수 블럭을 호출부에서 정의할 수 있습니다.
Composble 은 클릭시 onClick() 함수를 호출할 뿐, 어떠한 상태도 가지지 않습니다. 실행되는 코드는 상위 스코프에서 지정해준 이벤트 코드이고요.
StateHoisting 은 Composable 재사용과 테스트를 위해 사용됩니다. 간단한 테스트만 하려고 해도, 특정 값을 내부에서 캡처하여 참조하는 경우, 클릭 이벤트가 내부에 정의되어있는 경우 등의 Composable 은 Preview 가 지원되지 않기 때문입니다.
Jetpack Compose 는 이름처럼 조합에 주안점을 둔 UI 툴킷이니, 재사용 가능성이 있거나 다수의 테스트가 보장되어야만 하는 Composable 이라면 State Hoisting 을 통해 Stateless 하게 관리하는 것이 중요해 보입니다.
'Android > Tech' 카테고리의 다른 글
[Jetpack Compose] Image 의 ContentScale (0) | 2022.12.18 |
---|---|
[Jetpack Compose] Clickable Ripple Effect 없애기 (0) | 2022.12.05 |
[Glide] Glide 의 Cache (0) | 2022.10.21 |
[Dagger-Hilt] ActivityScope 와 ActivityRetainedScope 의 차이 (0) | 2022.10.21 |
Coroutine Dispatcher 란 무엇인가? (0) | 2022.10.19 |