Interview QuestionPractical QuestionFollow-up Questions

compositionLocalOf vs staticCompositionLocalOf

skydovesJaewoong Eum (skydoves)||9 min read

compositionLocalOf vs staticCompositionLocalOf

Jetpack Compose provides CompositionLocal as a mechanism to pass data implicitly down the composition tree without threading it through every composable parameter. There are two factory functions for creating a CompositionLocal: compositionLocalOf and staticCompositionLocalOf. They differ in how changes to the provided value affect recomposition, which directly impacts performance. By the end of this lesson, you will be able to:

  • Explain the purpose of CompositionLocal and how it passes data through the tree.
  • Describe how compositionLocalOf tracks readers and triggers targeted recomposition.
  • Describe how staticCompositionLocalOf skips recomposition tracking entirely.
  • Identify when each variant is appropriate based on whether the value changes during composition.
  • Predict the recomposition behavior when the provided value changes for each variant.

How CompositionLocal Works

A CompositionLocal holds a default value and can be overridden at any point in the tree using CompositionLocalProvider. Any composable below the provider in the tree can read the current value through the current property. This avoids passing the value explicitly through every intermediate composable, which is sometimes called "prop drilling."

CompositionLocal is scoped to the subtree where it is provided. A provider at the root affects the entire tree. A provider nested inside a specific screen affects only that screen and its children. Multiple providers for the same CompositionLocal can exist at different levels, and the closest ancestor provider wins.

The two factory functions differ in what happens when the provided value changes.

compositionLocalOf: Recomposition Aware

compositionLocalOf tracks which composables read its current value. When the provided value changes, only those composables that actually read it are recomposed. This makes it suitable for values that are expected to change during the lifetime of the composition, such as theme modes, user settings, or localization data.

val LocalThemeMode = compositionLocalOf { ThemeMode.Light }

@Composable
fun ThemeWrapper(
    themeMode: ThemeMode,
    content: @Composable () -> Unit
) {
    CompositionLocalProvider(
        LocalThemeMode provides themeMode
    ) {
        content()
    }
}

When themeMode changes, every composable that reads LocalThemeMode.current recomposes to reflect the new value. Composables that do not read it are unaffected.

staticCompositionLocalOf: No Recomposition Tracking

staticCompositionLocalOf does not track readers. When the provided value changes, Compose does not know which composables depend on it and cannot trigger targeted recomposition. The composables that read current will not recompose in response to a value change unless they recompose for another reason.

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor