JetpackCompose(9) - CompositionLocal

김재원·2022년 3월 18일
2

JetpackCompose

목록 보기
9/9
post-thumbnail

이 글은 JetpackCompose 공식문서 - CompositionLocal를 읽어보면서 정리한 글입니다.

CompositionLocal은 암시적으로 컴포지션을 통해 데이터를 전달하는 도구입니다.

이번 글에서는 Compose의 CompositionLocal의 자세한 내용, 자체 CompositionLocal을 만드는 법, CompositionLocal이 어떤 상황에 적합한 솔루션인지에 대해 알아보도록 하겠습니다.😌.

CompositionLocal 소개

일반적으로 Compose에서는 데이터가 각 Composable함수의 매개변수로 UI트리를 통해 아래로 흐릅니다. 따라서 Composable의 종속 항목이 명시적으로 됩니다.
하지만 이 방법은 색상, 스타일과 같이 널리 사용되는 데이터의 경우 번거러울 수 있습니다.

이런 경우를 위해 Compose는 CompositionLocal을 지원합니다.
이를 통해 UI 트리를 통해 데이터 흐름이 발생하는 암시적 방법으로 사용할 수 있는 트리 범위의 명명된 객체를 만들 수 있습니다.

CompositionLocal은 머티리얼 테마(MaterialTheme)에서 내부적으로 사용됩니다.

MaterialTheme: 컴포지션의 하위 부분에서 검색할 수 있는 CompositionLocal 인스턴스 세 개(색상, 서체, 도형)를 제공하는 객체입니다.
LocalColors, LocalShapes, LocalTypography 속성으로, MaterialTheme colors, 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에 제공하려면 CompositionLocalProviderCompositionLocalvalue에 연결하는 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을 사용할 땐 신중해야합니다. 다음과 같은 단점들이 있기 때문입니다.

  • Composable의 동작을 추론하기 어렵게 만듭니다.
  • 모든 부분에서 변경될 수 있기때문에 문제가 생겼을 때 앱을 디버깅하는것이 어려울 수 있습니다.

CompositionLocal 사용 여부 결정

다음 조건을 만족할 시 CompositionLocal을 사용하는것이 적합할 수 있습니다.

  • 적절한 기본값이 있는가 - 기본값이 없으면 테스트를 만들거나 CompositionLocal을 사용하는 Composable을 미리볼 때 값을 항상 명시해줘야하는 불편함이 생길 수 있습니다.
  • 트리 범위 또는 하위 계층 구조 범위로 간주되지 않는가 - CompositionLocal은 일부 하위요소가 아닌 모든 하위요소에서 사용될 때 유용합니다.

이 두 조건을 만족하지 않는다면 다른 방법을 찾아보는것을 권고합니다.
고려할 대안에 대해서는 밑에서 좀 더 자세히 알아보도록 하겠습니다.

CompositionLocal 만들기

CompositionLocal을 만드는 API는 두 가지가 있습니다.

  • compositionLocalOf: 재구성 중에 제공된 값을 변경하면 current 값을 읽는 콘텐츠만 무효화됩니다.
  • staticCompositionLocalOf: compositionLocalOf와 달리 staticCompositionLocalOf 읽기는 Compose에서 추적하지 않습니다.
    값을 변경하면 컴포지션에서 current 값을 읽는 위치만이 아니라 CompositionLocal이 제공된 content 람다 전체가 재구성됩니다.

CompositionLocal에 저장한 값이 변경되지 않을 값이라면 성능이 더 좋은staticCompostionLocalOf를 사용하는것이 좋습니다.

CompositionLocal에 값 제공

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 사용

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가 편하다는것을 알 수 있었습니다. 끝까지 읽어주셔서 감사하고 즐거운 개발 하세요🤗.

참고
CompositionLocal을 사용한 로컬 범위 지정 데이터

profile
항상 배울 것을 찾는 개발자입니다🔥.

0개의 댓글