CompositionLocal
on Android, Compose
일반적으로 Compose의 상태는 위에서 아래로 흐르는 단방향 데이터 흐름 패턴을 사용한다. 그러나 자주 널리 사용되는 데이터를 단방향 데이터 흐름을 적용하면 번거로울 수 있다.
@Composable
fun MyApp() {
// Theme information tends to be defined near the root of the application
val colors = …
}
// Some composable deep in the hierarchy
@Composable
fun SomeTextLabel(labelText: String) {
Text(
text = labelText,
color = // ← need to access colors here
)
}
CompositionLocal은 Composition을 통해 데이터를 암시적으로 전달한다.
@Composable
fun MyApp() {
// Provides a Theme whose values are propagated down its `content`
MaterialTheme {
// New values for colors, typography, and shapes are available
// in MaterialTheme's content lambda.
// ... content here ... }
}
// Some composable deep in the hierarchy of MaterialTheme
@Composable
fun SomeTextLabel(labelText: String) {
Text(
text = labelText,
// `primary` is obtained from MaterialTheme's
// LocalColors CompositionLocal
color = MaterialTheme.colors.primary
)
}
CompositionLocal의 current
값은 Composition의 상위 Composable에서 제공한 부분의 가장 가까운 값에 대응한다.
CompositionLocal에 값을 제공하기 위해서는 provides
라는 infix 함수를 이용한다.
@Composable
fun CompositionLocalExample() {
MaterialTheme { // MaterialTheme sets ContentAlpha.high as default
Column {
Text("Uses MaterialTheme's provided alpha")
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text("Medium value provided for LocalContentAlpha")
Text("This Text also uses the medium value")
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
DescendantExample()
}
}
}
}
}
@Composable
fun DescendantExample() {
// CompositionLocalProviders also work across composable functions
Text("This Text uses the disabled alpha now")
}
CompositionLocal은 암시적으로 Composition에 값을 전달하기 때문에 남발하면 Composable의 추론이 어려울 수 있으며 예상치 못한 결과를 일으킬 수 있다. 또한 CompositionLocal의 값은 Composition의 어떤 부분에서도 변경 가능하기 때문에 명확한 정보 소스를 찾기 어렵다.
CompositionLocal 사용 여부 결정
- 적절한 기본값이 필요: 기본값이 없으면 CompositionLocal에 값이 제공되지 않을 수 있는 문제가 생긴다.
- 모든 하위 요소에서 사용할 때 적합
CompositionLocal 만들기
compositionLocalOf
: 값을 변경하면 current 값을 읽는 콘텐츠의 재구성staticCompositionLocalOf
: Compose에서 값을 추적하지 않음. 따라서 값을 변경하면 CompositionLocal이 적용된 content 람다 전체가 재구성 CompositionLocal의 값이 자주 변경되지 않을 것이라는게 확실할 경우staticCompositionLocalOf
를 사용하여 성능 이점을 얻을 수 있다.
// LocalElevations.kt file
data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp)
// Define a CompositionLocal global object with a default
// This instance can be accessed by all composables in the app
val LocalElevations = compositionLocalOf { Elevations() }
CompositionLocal에 값 제공
CompositionLocalProvider
는 주어진 계층 구조의 CompositionLocal에 값을 바인딩한다.
// MyActivity.kt file
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Calculate elevations based on the system theme
val elevations = if (isSystemInDarkTheme()) {
Elevations(card = 1.dp, default = 1.dp)
} else {
Elevations(card = 0.dp, default = 0.dp)
}
// Bind elevation as the value for LocalElevations
CompositionLocalProvider(LocalElevations provides elevations) {
// ... Content goes here ...
// This part of Composition will see the `elevations` instance
// when accessing LocalElevations.current
}
}
}
}
CompositionLocal 사용
@Composable
fun SomeComposable() {
// Access the globally defined LocalElevations variable to get the
// current Elevations in this part of the Composition
Card(elevation = LocalElevations.current.card) {
// Content
}
}