Jetpack Compose CompositionLocal + Diagram + State 비교 예시

Jetpack Compose CompositionLocal

Jetpack Compose의 CompositionLocal은 UI 트리 아래로 환경값을 전달하는 강력한 메커니즘이다.
CompositionLocal in Jetpack Compose is a powerful mechanism for providing environment values down the UI tree.


📌 0. CompositionLocal 개념 요약 (Concept Summary)

CompositionLocal은 상위 → 하위로 값을 전달하는 Ambient Context 시스템이다.
CompositionLocal is an ambient context system for delivering values from parent → child.

주로 다음 용도로 사용한다:
Used commonly to deliver:

  • Context / View 같은 안드로이드 환경 요소
  • Theme / Typography / ContentColor 같은 디자인 시스템 값
  • 포커스 / 키보드 / Lifecycle 같은 UI 시스템 값
  • App configuration, Feature Flags 같은 환경 정보

📌 1. CompositionLocal 구조 다이어그램 (Diagram)

아래는 CompositionLocal의 값 전달 구조를 단순화한 한 컷 모델이다.
Below is a simplified one-cut mental model of how CompositionLocal works.

Root
 └─ CompositionLocalProvider(LocalA = A1, LocalB = B1)
       ├─ ScreenA
       │     └─ ComponentA1 (LocalA = A1)
       └─ ScreenB
             └─ CompositionLocalProvider(LocalA = A2)
                   └─ ComponentB1 (LocalA = A2, LocalB = B1)

핵심 개념:
Key ideas:

  • CompositionLocal은 값이 “가장 가까운 provider”에서 결정된다.
    Value resolves from the nearest provider in the tree.
  • 파라미터 전파가 불필요하다.
    No need to pass through parameters.
  • provider를 중첩하면 override된다.
    Nested providers override parent providers.

📌 2. 기본 사용 패턴 (Basic Usage Pattern)

val LocalGreeting = compositionLocalOf { "Hello" }

@Composable
fun GreetingHost() {
    CompositionLocalProvider(LocalGreeting provides "안녕") {
        Child()
    }
}

@Composable
fun Child() {
    Text(LocalGreeting.current)
}

📌 3. 주요 CompositionLocal 정리 (Major CompositionLocals)

✔ LocalContext

Android Context 제공
Provides Android Context

사용 예시(Toast, Intent, resources):

val context = LocalContext.current
Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()

✔ LocalView

Compose가 그려지는 실제 Android View
The actual Android View hosting Compose

사용 예시(WindowInsetsController 등):

val view = LocalView.current
if (!view.isInEditMode) {
    SideEffect { setupEdgeToEdge(view) }
}

✔ LocalDensity

dp/sp ↔ px 변환을 위한 Density 제공
Provides Density for converting dp/sp ↔ px

val px = with(LocalDensity.current) { 16.dp.toPx() }

✔ LocalConfiguration

화면 사이즈, 로케일, 회전 정보 제공
Provides configuration like screen size, locale, orientation

if (LocalConfiguration.current.orientation == ORIENTATION_LANDSCAPE) { ... }

✔ LocalLifecycleOwner

LifecycleOwner 접근 제공
Access to LifecycleOwner

✔ LocalFocusManager / LocalSoftwareKeyboardController

포커스 이동과 키보드 제어 제공
Focus movement and keyboard control


📌 4. CompositionLocal vs remember / rememberSaveable / derivedStateOf 비교

Compose 상태 모델과 CompositionLocal의 역할을 명확히 구분할 필요가 있다.
It’s important to clearly differentiate CompositionLocal from state models.

✔ 비교 테이블 (Comparison Table)

기능 CompositionLocal remember rememberSaveable derivedStateOf
목적 환경값 전달 단일 구성에서 값 기억 프로세스 복원 가능한 기억 기존 상태에서 파생된 메모이즈 상태
데이터 성격 Context-like / Theme-like UI State UI State + SavedState Derived UI value
트리 전파 Yes No No No
recomposition current 읽을 때 영향 값 변경 시 UI 갱신 저장/복원 목적 dependency 값 변경 시 갱신
LocalView, LocalContext TextField state Form inputs filtered list

✔ 정적 예시 (Static Example)

val LocalUser = compositionLocalOf<User?> { null }

@Composable
fun Screen() {
    CompositionLocalProvider(LocalUser provides User("Admin")) {
        UserPanel()
    }
}

@Composable
fun UserPanel() {
    val user = LocalUser.current      // global-like context
    var count by remember { mutableStateOf(0) }     // per-composable state
}

📌 5. 실전 프로젝트 구조 내 CompositionLocal 설계 예시

(Real Project Architecture Examples)


✔ 예시 1 — AppConfig (Feature Flags + App-level Settings)

파일 구조

ui/
  theme/
    Color.kt
    Type.kt
    Shape.kt
    Theme.kt
    AppConfig.kt   ← 추가

AppConfig.kt

data class AppConfig(
    val useNewChatUI: Boolean,
    val apiEndpoint: String,
)

val LocalAppConfig = staticCompositionLocalOf<AppConfig> {
    error("AppConfig not provided")
}

적용

@Composable
fun MyApp(appConfig: AppConfig) {
    CompositionLocalProvider(LocalAppConfig provides appConfig) {
        AppRoot()
    }
}

@Composable
fun SomeScreen() {
    val config = LocalAppConfig.current
    if (config.useNewChatUI) NewChat() else OldChat()
}

✔ 예시 2 — Design System Tokens

Typography, Color, Shape 외에도
Spacing, Elevation, MotionSpec 같은 항목도 CompositionLocal 로 제공할 수 있다.

Spacing.kt

data class Spacing(
    val small: Dp = 4.dp,
    val medium: Dp = 12.dp,
    val large: Dp = 24.dp
)

val LocalSpacing = staticCompositionLocalOf { Spacing() }

사용

Column(Modifier.padding(LocalSpacing.current.medium)) { ... }

✔ 예시 3 — Locale-aware CompositionLocal (로케일 반영 전략)

Locale, LayoutDirection, Formatting Rules 등을 일관되게 전달할 수 있다.

data class LocaleConfig(
    val locale: Locale,
    val dateFormatter: DateFormat,
)

val LocalLocaleConfig = compositionLocalOf<LocaleConfig> {
    error("LocaleConfig not provided")
}

📌 6. CompositionLocal 남용에 대한 주의 사항

(Cautions about Overuse)

  • 전역 상태처럼 변하면 유지보수가 어려워짐
    Overusing turns it into global state
  • 비즈니스 로직 전달에 사용하면 안 됨
    Should not carry business logic
  • “환경 값 전달용”에만 사용해야 안전
    Safe only for environment-like values

📌 7. 최종 요약 (Final Summary)

  • CompositionLocal은 환경값 전달 전용 메커니즘이다.
    It is a mechanism specifically for delivering environment values.
  • 상태 보관은 remember / rememberSaveable 를 사용해야 한다.
    Use remember / rememberSaveable for state.
  • derivedStateOf는 상태에서 파생 값 계산에 사용한다.
    derivedStateOf is for derived values.
  • 실전 프로젝트에서는 Theme 시스템 + Custom Local 조합으로
    강력한 UI/UX 디자인 시스템을 구축할 수 있다.
    Real projects combine custom locals with the theme system to build strong design systems.