이 글은 JetpackCompose 공식문서 - CompositionLocal를 읽어보면서 정리한 글입니다.
CompositionLocal은 암시적으로 컴포지션을 통해 데이터를 전달하는 도구입니다.
이번 글에서는 Compose의 CompositionLocal의 자세한 내용, 자체 CompositionLocal을 만드는 법, CompositionLocal이 어떤 상황에 적합한 솔루션인지에 대해 알아보도록 하겠습니다.😌.
일반적으로 Compose에서는 데이터가 각 Composable함수의 매개변수로 UI트리를 통해 아래로 흐릅니다. 따라서 Composable의 종속 항목이 명시적으로 됩니다.
하지만 이 방법은 색상, 스타일과 같이 널리 사용되는 데이터의 경우 번거러울 수 있습니다.
이런 경우를 위해 Compose는 CompositionLocal
을 지원합니다.
이를 통해 UI 트리를 통해 데이터 흐름이 발생하는 암시적 방법으로 사용할 수 있는 트리 범위의 명명된 객체를 만들 수 있습니다.
CompositionLocal은 머티리얼 테마(MaterialTheme
)에서 내부적으로 사용됩니다.
MaterialTheme: 컴포지션의 하위 부분에서 검색할 수 있는 CompositionLocal 인스턴스 세 개(
색상
,서체
,도형
)를 제공하는 객체입니다.
LocalColors
,LocalShapes
,LocalTypography
속성으로, MaterialThemecolors
,shapes
,typography
속성을 통해 액세스할 수 있습니다.
@Composable
fun MyApp() {
MaterialTheme {
// New values for colors, typography, and shapes are available
// in MaterialTheme's content lambda.
// ... content here ...
}
}
@Composable
fun SomeTextLabel(labelText: String) {
Text(
text = labelText,
// `primary` is obtained from MaterialTheme's
// LocalColors CompositionLocal
color = MaterialTheme.colors.primary // CompositionLocal 사용
)
}
새 값을 Composition에 제공하려면 CompositionLocalProvider
와 CompositionLocal
를 value
에 연결하는 provides
중위 함수를 사용합니다.
@Composable
fun CompositionLocalExample() {
MaterialTheme { // MaterialTheme 은 ContentAlpha.high 를 기본값으로 설정합니다.
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은 다른 composable함수에도 적용됩니다.
Text("This Text uses the disabled alpha now")
}
CompositionLocalProvider의 content 람다는 CompositionLocal의 current 속성에 액세스할 때 제공된 값을 가져옵니다. 새 값이 제공되면 Compose는 CompositionLocal을 읽는 컴포지션의 부분을 재구성합니다.
CompositionLocal
은 암시적 컴포지션을 통해 데이터를 전달하는 도구입니다. 매개변수가 크로스 커팅이거나 중간 레이어가 그 존재를 인식해선 안될 때 사용합니다.
CompositionLocal
을 사용할 땐 신중해야합니다. 다음과 같은 단점들이 있기 때문입니다.
다음 조건을 만족할 시 CompositionLocal을 사용하는것이 적합할 수 있습니다.
CompositionLocal
을 사용하는 Composable을 미리볼 때 값을 항상 명시해줘야하는 불편함이 생길 수 있습니다.CompositionLocal
은 일부 하위요소가 아닌 모든 하위요소에서 사용될 때 유용합니다.이 두 조건을 만족하지 않는다면 다른 방법을 찾아보는것을 권고합니다.
고려할 대안에 대해서는 밑에서 좀 더 자세히 알아보도록 하겠습니다.
CompositionLocal
을 만드는 API는 두 가지가 있습니다.
compositionLocalOf
: 재구성 중에 제공된 값을 변경하면 current 값을 읽는 콘텐츠만 무효화됩니다.staticCompositionLocalOf
: compositionLocalOf
와 달리 staticCompositionLocalOf
읽기는 Compose에서 추적하지 않습니다.CompositionLocal에 저장한 값이 변경되지 않을 값이라면 성능이 더 좋은staticCompostionLocalOf
를 사용하는것이 좋습니다.
CompositionLocalProvider
Composable은 주어진 계층 구조의 CompositionLocal
인스턴트에 값을 바인딩 합니다.
새값을 제공하려면 CompositionLocal
키를 value에 연결하는 provide
중위함수를 사용하면 됩니다.
setContent {
val elevations = if (isSystemInDarkTheme()) {
Elevations(card = 1.dp, default = 1.dp)
} else {
Elevations(card = 0.dp, default = 0.dp)
}
CompositionLocalProvider(LocalElevations provides elevations) {
//provides중위 함수를 사용하여 값을 연결하였습니다.
// ... Content goes here ...
// This part of Composition will see the `elevations` instance
// when accessing LocalElevations.current
}
}
CompositionLocal.current
를 통해 값을 가져올 수 있습니다.
CompositionLocal.current
는 CompositionLocal에 값을 제공하는 가장 가까운 CompositionLocalProvider가 제공한 값을 반환합니다.
@Composable
fun SomeComposable() {
// 그림자를 넣기 위해 전역으로 정의된 LocalElevations의 값을 가져옵니다.
Card(elevation = LocalElevations.current.card) {
// Content
}
}
CompositionLocal이 항상 옳은 솔루션은 아닙니다. CompositionLocal이 적합하지 않다고 판단될 땐 다음 방법들을 사용하는 것을 고려해야합니다.
매개변수로 필요한 값을 전달하는 것 입니다. Composable에는 필요한 것만 전달하는 것이 좋습니다. Composable의 분리와 재사용을 위해서 Composable은 최대한 적은 정보를 갖고있어야 합니다.
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
MyDescendant(myViewModel.data)
}
@Composable
fun MyDescendant(data: DataToDisplay) {
// Display data
}
하위 요소가 로직을 실행하지 않고, 상위 요소가 대신 실행해 주는 방법입니다.
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
MyDescendant(myViewModel)
}
@Composable
fun MyDescendant(myViewModel: MyViewModel) {
Button(onClick = { myViewModel.loadData() }) {
Text("Load data")
}
}
위 예시코드에서 MyDescendant는 MyViewModel에 종속되기 때문에 재사용하기 어렵습니다. 따라서 상위 요소가 로직을 실행하도록 제어 역전 원칙을 사용하는 대안을 고려하는것이 좋습니다.
@Composable
fun MyComposable(myViewModel: MyViewModel = viewModel()) {
// ...
ReusableLoadDataButton(
onLoadClick = {
myViewModel.loadData()
}
)
}
@Composable
fun ReusableLoadDataButton(onLoadClick: () -> Unit) {
Button(onClick = onLoadClick) {
Text("Load data")
}
}
이렇게 해서 Compose의 CompositionLocal에 대해 알아보았습니다. JetpackCompose의 공식문서에 있는 기초내용들을 읽어보면서 정리해보았는데, Compose에서 제공해주는 기능들이 많아서 확실히 Compose가 편하다는것을 알 수 있었습니다. 끝까지 읽어주셔서 감사하고 즐거운 개발 하세요🤗.