Locally scoped data with CompositionLocal
CompositionLocal
은 구성상에서 내제하여 데이터를 전달하기위한 도구입니다. 이 장에서는, CompositionLocal
이 무엇인지, 직접 생성하는 방법과 어떤 사례에 적합한 솔루션인지 좀 더 자세히 배울 수 있습니다.
Introducing CompositionLocal
보통 컴포즈에서는, 데이터는 UI 트리를 구성하는 각 컴포저블 함수의 파라미터로 아래로 흐릅니다.(data flows down) 이것이 컴포저블간 종속성을 분명하게 해줍니다. 그러나 색상이나 스타일 타입같이 자주, 널리 사용되는 데이터의 경우 매우 번거로울 수 있습니다. 다음 예를 참조하세요:
대부분의 컴포저블에 명시적 파라미터로 색상을 전달할 필요가 없도록, 컴포즈는 UI 트리안에서 데이터가 흐를 수 있게 사용 가능한 트리-범위 내 명명된 개체를 만들 수 있는 CompositionLocal
를 제공합니다.
ComposititionLocal 은 대게 UI 트리의 일부 노드에 값을 제공합니다. 이 값은 컴포저블 함수의 매개변수로 CompositionLocal
을 선언하지 않고도 컴포저블 하위 항목이 사용할 수 있습니다.
주요 용어 : 이 가이드에서는 구성, UI 트리, 그리고 UI 계층이라는 용어를 사용합니다. 다른 가이드에서 서로 바꿔 사용될 수 있지만, 이것들의 의미는 차이가 있습니다. 구성(Composition) 은 컴포저블 함수들의 호출 그래프의 기록입니다. UI 트리나 UI 계층은 구성 프로세스로부터 생성, 업데이트 및 유지되는 LayoutNode 입니다.
CompositionLocal
은 머터리얼 테마안을 들여다 보면 확인 가능합니다. MaterialTheme
는 세 가지 CompositionLocal 객체를 제공합니다 — 색상, 타이포그래피, 도형 — 이를 이용하여 나중에 구성의 어떤 하위 요소에서도 검색할 수 있습니다. 특히, 머터리얼 테마의 색상, 도형, 타이포그래피 속성에 접근 가능한 LocalColors
, LocalShapes
, LocalTypography
프로퍼티가 있습니다.
CompositionLocal
객체는 구성의 일부로 범위가 지정되므로 트리 서로 다른 단계들 마다 서로 다른 값을 제공할 수 있습니다. CompositionLocal 의 current
는 구성에서 해당 요소의 상위에서 제공한 가장 가까운 값에 해당합니다.
CompositionLocal 에 새 값을 제공하려면, CompositionLocalProvider
와 CompositionLocal
키를 값에 연결하는 provides
중위 함수(infix function) 를 사용합니다. ContentLocalProvider 의 current 람다는 CompositionLocal.current 프로퍼티 접근 시 획득하는 값을 제공합니다. 새로운 값이 제공될 때, 컴포즈는 CompositionLocal 을 읽는 구성 일부를 재구성합니다.
이 예에서는, LocalContentAlpha
CompositionLocal 는 UI 다른 부분에 대해서 더 강조하거나 덜 강조하기 위해 텍스트 및 아이콘에 사용되는 컨텐츠 투명도가 포함되어 있습니다. CompositionLocalProvider
가 서로 다른 구성에 대하여 서로 다른 값을 제공하기 위해 사용됩니다.
이 예제에서는, CompositionLocal 인스턴스는 머터리얼 컴포저블에 의해 내부적으로 사용됩니다. CompositionLocal 의 현재 값에 접근하려면, current
프로퍼티를 사용하면 됩니다. 다음 예제에서는 안드로이드에서 일반적으로 문자열 형식을 LocalContext
CompositionLocal 의 현재 Context
값으로 사용하는 방법입니다.
참조 : CompositionLocal 객체 및 상수는 일반적으로 IDE 환경에서 좀 더 자동완성기능을 효율적으로 사용하기 위하여 Local 접두사를 사용합니다.
Creating your own CompositionLocal
CompositionLocal
은 구성을 통해 데이터를 암묵적으로 전달하기 위한 도구입니다.
CompositionLocal 을 사용하기 위한 또 다른 주요 상황은 매개변수가 교차하는 경우이고, 중간 계층에서 존재하는 것(=CompositionLocal 이 존재하는것)을 인지하지 못하게 하는 것인데, 이는 중간 계층의 컴포저블의 효용성이 제한될 수 있기 때문입니다. 예를들어, Android 권한 요청은 내부적으로 CompositionLocal 에서 제공됩니다. 기기 내 컨텐츠 접근에 대해 API 변경과 관계없이 권한으로부터 보호되는 미디어 선택(media picker) 컴포저블이 새롭게 추가되었으며, 미디어 선택기 호출자 환경에서 사용되는 컨텍스트가 요구됩니다.
그러나, CompositionLocal 이 항상 최선의 해결책은 아닙니다. CompositionLocal 은 다음과 같은 몇가지 단점이 있기 때문에 남용 되어서는 안됩니다:
CompositonLocal 은 컴포저블이 어떻게 동작하는지 추론하기 어렵게 만듭니다. 암시적으로 종속성이 분리된 채 생성되기 때문에, 컴포저블 호출자는 모든 CompositionLocal 에 대한 값이 충족되는지 확인해야 합니다.
더욱이, 이 의존성(=CompositionLocal 에 대한 의존성)은 구성의 어떠한 부분에서든지 변이가 가능하므로 진실의 원천이 없을 수 있습니다. 그러므로, 구성에 current
값이 제공되는 위치를 확인하기 위해 상위를 탐색해야 하므로 이슈가 생겼을때 앱을 디버깅하는 것이 더 어렵습니다. IDE 의 사용탐색(Find usages) 이나 Compose layout inspector 의 정보로 이러한 문제를 완화해줄 수 있는 충분한 정보를 확인할 수 있습니다.
참조 : CompositionLocal 은 기본 아키텍처에 적합하고, Jetpack Compose 에서 많이 사용합니다.
Deciding whether to use CompositionLocal
CompositionLocal
을 사용하기에 좋은 사례로 만들 적절한 상황이 있습니다:
CompositionLocal
은 기본값(default value)을 가지고 있기에 적절합니다. 만약 기본 값이 없다면, CompositionLocal
값이 제공되지 않는 상황에 들어가는 것이 매우 어렵다는 것을 개발자가 보장해야 합니다. 기본 값을 제공하지 않는것은 테스트나 CompositionLocal
을 사용하는 컴포저블 미리보기를 만들 때 문제가 생기거나 불안할 수 있습니다.
트리 범위나 하위 계층 범주로 간주되지 않는 개념이라면 CompositionLocal 을 사용하면 안됩니다. CompositionLocal
은 일부 하위 요소가 아닌, 하위요소 전반에서 잠재적으로 사용될 수 있을 때 유용합니다.
만약 이러한 요구조건이 충족되지 않는다면, CompositionLocal
사용 전에 대체 전 고려할 사항 섹션을 확인해 보세요.
한가지 잘못 사용한 예로, 어떤 화면의 ViewModel
을 CompositionLocal
가 가지고 있고, 해당 화면의 모든 컴포저블이 뷰모델의 일부 로직을 실행할 수 있게되는 것입니다. UI 트리의 모든 컴포저블이 ViewModel 에 대해 알아야 하는 것이 아니므로 잘못된 방법입니다. 좋은 사례는 컴포저블이 필요한 정보만 받고, 이벤트를 전달하는 것입니다. 이렇게 하면 컴포저블은 좀 더 재사용성이 높아지고 쉽게 테스트 할 수 있습니다.
Creating a CompositionLocal
CompositionLocal
생성을 위한 두 가지 API 가 있습니다:
compositionLocalOf
: 재구성 중 제공된 값이 변경되면 current 값을 읽는 컨텐츠만 무효화(invalidates) 됩니다.staticCompositionLocalOf
:compositionLocalOf
와 다르게staticCompositionLocalOf
값을 읽는것은 컴포즈가 추적하지 않습니다. 값이 변경되면 구성 안에서current
값을 읽는 컴포저블 대신 CompositionLocal 에서 람다로 제공되는content
전체가 재구성 됩니다.
CompositionLocal 에서 제공되는 값이 거의 변경되지 않거나 혹은 아에 변경되지 않는다면, staticCompositionLocalOf
를 이용하여 성능상 이점을 얻을 수 있습니다.
예를들어, 앱 디자인 시스템은 UI 컴포넌트에 자신만의(디자인시스템만의) 그림자 효과를 적용하는 방식을 고려할 수 있습니다. 앱 내 서로 다른 엘레베이션이 있고, UI 트리 전체에 전파되어야 하므로, ComposeLocal 을 사용합니다. CompositionLocal 값은 시스템 테마에 기반하여 조건부 파생되므로 compositionLocalOf
API 를 사용합니다.
Providing values to a CompositionLocal
CompositionLocalProvider
컴포저블은 주어진 계층구조에 CompositionLocal 인스턴스와 값을 바인딩합니다. CompositionLocal 에 새로운 값을 제공하려면, 다음과 같이 provides
중위 함수를 이용하여 CompositionLocal 키에 값을 연결합니다.
Consuming the CompositionLocal
CompositionLocal.current
는 가장 가까운 CompositionLocalProvider 이 제공하는 CompositionLocal 의 값을 제공합니다 :
Alternatives to consider
CompositionLocal
은 몇몇 경우에는 과도한 해결책이기도 합니다. 만약 당신의 요구사항이 CompositionLocal 사용 결정 섹션에 지정된 기준을 충족하지 않다면, 다른 해결책이 당신의 사례에 더 적합할 수 있습니다.
Pass explicit parameters
컴포저블의 의존성을 명시적으로 분리하는 것은 좋은 습관입니다. 우리는 컴포저블이 오직 필요로 하는 것만을 전달하는것을 추천합니다. 컴포저블의 응집도를 낮추고 재사용하기 위해, 각 컴포저블은 최소한의 정보만을 보유해야 합니다.
Inversion of control
컴포저블의 불필요한 의존성 전달을 막는 또 다른 방법은 제어역전(Inversion of control)입니다. 의존성이 있는 몇가지 로직을 하위 요소가 처리하는 대신, 상위 요소에서 이를 대신합니다.
하위 요소가 일부 데이터를 로드하기 위해 요청하는 것을 예로 살펴보겠습니다:
이 예시는 경우에 따라서, MyDescendant
가 큰 책임을 가질 수 있습니다. 또한, MyViewModel
을 종속성으로 전달하여 이 둘이 결합되었기 때문에 MyDescendant
재사용성이 줄어듭니다. 종속성을 하위요소로 전달하지 않고 상위에서 논리의 실행을 책임지도록 제어역전을 사용할 수 있도록 대안을 생각해 보세요.
이 접근방식은 상위 요소와의 결합도를 분리하이 몇몇 사용 사례(=재사용성적인 측면)에 대하여 적합할 수 있습니다. 상위 컴포저블은 하위 컴포저블들을 유연하게 갖는것을 선호하여, 보다 복잡해지는 경향이 있습니다.
마찬가지로 @Composable 람다 컨텐츠를 같은 방식으로 사용하여 동일한 이점을 얻을 수 있습니다.