11. Jan 2022Android

Jetpack Compose Basics - How to use and create own CompositionLocal

In today's episode of the Jetpack Compose basics series, I'll show you how to use CompositionLocal to implicitly pass data via composition, which will make function parameters clearer.

Peter ŠulyAndroid developer

What is CompositionLocal in Jetpack Compose?

How can you pass data down through composition? One way is to send them as function parameters to each composable explicitly, but in case of let's say colors or typography things can get pretty messy because you need them in almost every composable.

Your second option is to use CompositionLocal. It's tool for passing data through composition implicitly. E.g. MaterialTheme uses CompositionLocal under the hood to provide colors, shapes and typography anywhere.

How to use CompositionLocal in Jetpack Compose?

Take a look at example code below. Color of texts is not changed directly in Text composable but LocalContentColor provider is used. This technique is widely used in Jetpack Compose framework and can be useful when you need to change attribute of all composables in scope. Current value of CompositionLocal corresponds to the closest value provided by ancestor in specified part of composition.

CompositionLocalProvider(LocalContentColor provides Color.Blue) {
		// content color for all components in this scope is blue
    Text(text = "Lorem")
    CompositionLocalProvider(LocalContentColor provides Color.Red) {
				// content color for all components in this scope is blue
        Text(text = "ipsum")
        Text(text = "dolor")
        CompositionLocalProvider(LocalContentAlpha provides 0.2f) {
						// alpha of all components in this scope is 0.2f
            Text(text = "sit...")
        }
    }
}
Example of using LocalContentColor
Example of using LocalContentColor

If your question is - How can Text composable know which color or alpha to use? - answer is in implementation of Text composable. Color of Text composable is resolved by LocalContentColor.current call unless caller doesn't specify the color attribute.

val textColor = color.takeOrElse 
    style.color.takeOrElse {
        LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
    }
}

How to create own CompositionLocal?

Firstly, CompositionLocal really makes sense especially when it can be potentially used by any descendant not by few of them. You should better think twice about whether you really want to create it or just use exmplicit parameters. It is not always the best solution and is not recommended to overuse it. Downside is that it's harder to make sure a value for every CompositionLocal is satisfied when dependencies are implicit.

Secondly, there should always be some value in CompositionLocal, while creation you default value should be provided.

There are 2 option to create Composition Local

  • compositionLocalOf - change of value invalidates only the content that reads its current value
  • staticCompositionLocalOf - reads are not tracked by Compose. Change of value causes entire content lambda to be recomposed, instead of just places where current value is read
// Definition with default value
val LocalPaddings = compositionLocalOf { PaddingValues(0.dp) }

.
.
.

// Using CompositionLocal App-wide
CompositionLocalProvider(LocalPaddings provides PaddingValues(24.dp)) {
		/*
			your app composables...
		*/
}

Reading from created LocalPaddings can look like this. Box in example below now have app wide paddings - PaddingValues(24.dp).

Box(
    modifier = Modifier.padding(LocalPaddings.current)
){
		// some composable padded by 24.dp
}

More articles from the Jetpack Compose Basics series:

Peter ŠulyAndroid developer