상태 호이스팅(State Hoisting)을 하게 된다면 상위 계층에서 상태를 관리하게 되고 하위 계층에 파라미터로 전달해서 사용할 수 있습니다. 만약, 계층의 여러 단계를 통해 상태를 전달해야 하는 경우 번거로울 수 있습니다. 하지만 컴포즈에서 제공하는 CompositionLocal을 사용한다면 이러한 문제를 해결할 수 있습니다.
CompositionLocal은 상위 계층의 컴포저블에서 선언된 상태를 하위 계층의 컴포저블에서 사용할 수 있도록 방법을 제공합니다. 모든 하위 계층에 상태에 대한 파라미터를 전달하지 않고도 가장 높은 노드에 선언된 상태를 하위 노드에서 이용할 수 있습니다.
따라서, CompositionLocal을 이용하면 값이 할당된 지점 하위의 컴포저블에서만 데이터를 이용할 수 있습니다.
Composable 3: 상태 관리
Composable 5: LazyListState에 대해서 접근할 필요가 없지만 하위 컴포저블인 Composition 8에게 전달하기 위해 상태값을 파라미터로 받음
Composable 8: 상위 컴포저블인 Composable 5로 부터 상태 값을 파라미터로 받음
Composable 3: CompositionLocal로 상태 관리
Composable 5, Composable 7, Composable 8: Composable 3의 하위 컴포저블로 파라미터로 상태 값을 받지 않아도 상태에 접근할 수 있음
CompositionLocal을 이용해서 상태를 선언하려면 ProvidableCompositionalLocal 인스턴스를 생성해야 합니다. 이 인스턴스를 얻기 위해서는 compositionLocalOf() 이나 staticCompositionLocalOf() 함수를 호출해서 얻을 수 있습니다.
ProvidableCompositionalLocal 인스턴스를 생성했다면 provides() 함수로 값을 할당합니다. CompositionLocalProvider 함수를 호출하여 생성한 인스턴스를 파라미터로 전달하고 하위 컴포저블도 파라미터로 전달하여 하위 컴포저블의 모든 자손에서 ProvidableCompositionalLocal 인스턴스의 현재 프로퍼티를 통해 CompositionLocal 상태에 접근할 수 있습니다.
val localNumber = compositionLocalOf{ 0 }
//val localNumber = staticCompostionLocalOf { 0 }
val number = 1
CompositionLocalProvider(localNumber provides number) {
ChildComposable()
}
val childNodeNumber = LocalNumber.current
fun <T> compositionLocalOf(
policy: SnapshotMutationPolicy<T> =
structuralEqualityPolicy(),
defaultFactory: () -> T
): ProvidableCompositionLocal<T> = DynamicProvidableCompositionLocal(policy, defaultFactory)
fun <T> staticCompositionLocalOf(defaultFactory: () -> T): ProvidableCompositionLocal<T> =
StaticProvidableCompositionLocal(defaultFactory)
compositionLocalOf() 와 staticCompositionLocalOf() 함수 모두 defaultFactory라는 파라미터로 기본값에 대한 람다를 받고 있기 때문에 기본값을 정의할 수 있습니다.
compositionLocalOf()staticCompositionLocalOf()위에서 봤던 이미지의 트리 형태로 Composable 함수로 트리를 구성했습니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GeminiSampleTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
Composable1()
}
}
}
}
}
val LocalNumber = staticCompositionLocalOf { 1 }
@Composable
fun Composable1() {
Column {
Composable2()
Composable3()
}
}
@Composable
fun Composable2() {
Composable4()
}
@Composable
fun Composable3() {
Composable5()
}
@Composable
fun Composable4() {
Composable6()
}
@Composable
fun Composable5() {
Composable7()
Composable8()
}
@Composable
fun Composable6() {
Text("Composable 6")
}
@Composable
fun Composable7() {
Text("Composable 7")
}
@Composable
fun Composable8() {
Text("Composable 8")
}
Composable1 함수 위에 staticCompositionLocalOf() 을 호출하고 Composable1 안에서 provides() 함수로 값을 할당합니다. CompositionLocalProvider() 함수에 파라미터로 하위 컴포저블인 Composable3()와 함께 전달합니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GeminiSampleTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
Composable1()
}
}
}
}
}
private val localNumber = staticCompositionLocalOf { 0 }
@Composable
fun Composable1() {
Column {
Composable2()
CompositionLocalProvider(localNumber provides 0) {
Composable3()
}
}
}
@Composable
fun Composable2() {
Composable4()
}
@Composable
fun Composable3() {
Composable5()
}
@Composable
fun Composable4() {
Composable6()
}
@Composable
fun Composable5() {
Composable7()
Composable8()
}
@Composable
fun Composable6() {
Text("Composable 6")
}
@Composable
fun Composable7() {
Text("Composable 7")
}
@Composable
fun Composable8() {
val number = localNumber.current
Text("Composable 8: $number")
}
Composable 3의 자손 컴포저블인 Composable 8에서 파라미터로 상태 값을 받지 않고도 상위 컴포저블에서 관리하는 상태에 접근할 수 있는 것을 알 수 있습니다.
컴포저블 3, 5, 7, 8에서 각각 다른 값을 불러오도록 구현할 수 있습니다. 각 컴포저블을 호출할 때 CompositionLocalProivder() 함수에서 다른 값을 할당하면 됩니다.
@Composable
fun Composable3() {
val number = localNumber.current
Text("Composable 3: $number")
CompositionLocalProvider(localNumber provides 1) {
Composable5()
}
}
@Composable
fun Composable4() {
Composable6()
}
@Composable
fun Composable5() {
val number = localNumber.current
Text("Composable 5: $number")
CompositionLocalProvider(localNumber provides 2) {
Composable7()
}
CompositionLocalProvider(localNumber provides 3) {
Composable8()
}
}
@Composable
fun Composable6() {
Text("Composable 6")
}
@Composable
fun Composable7() {
val number = localNumber.current
Text("Composable 7: $number")
}
@Composable
fun Composable8() {
val number = localNumber.current
Text("Composable 8: $number")
}