1

I'm trying to understand how CompositionLocal actually sets values implicitly and what requiremnts need to be met so that it works, but Android's documentation about Locally scoped data with CompositionLocal isn't helping.

There is an example where the color of a Text is being changed by assigning a new value to the color parameter.

// Some composable deep in the hierarchy of MaterialTheme
@Composable
fun SomeTextLabel(labelText: String) {
    Text(
        text = labelText,
        // `primary` is obtained from MaterialTheme's
        // LocalColors CompositionLocal
        color = MaterialTheme.colors.primary
    )
}

But this is not implicit if you need to set it this way!

Then they show another example where they change the ContentAlpha via the CompositionLocalProvider and here the Text suddenly can use the new value implicitly even though they write that CompositionLocal is what the Material theme uses under the hood. so why doesn't it work with the first example?

@Composable
fun CompositionLocalExample() {
    MaterialTheme { // MaterialTheme sets ContentAlpha.high as default
        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 also work across composable functions
    Text("This Text uses the disabled alpha now")
}

There's also a 3rd example where they show how to create your own CompositionLocal, but here agian, they explicitly set the Card's elevation parameter!

@Composable
fun SomeComposable() {
    // Access the globally defined LocalElevations variable to get the
    // current Elevations in this part of the Composition
    Card(elevation = LocalElevations.current.card) {
        // Content
    }
}

Didn't they just create the CompositionLocalProvider to avoid doing this?

            // Bind elevation as the value for LocalElevations
            CompositionLocalProvider(LocalElevations provides elevations) {
                // ... Content goes here ...
                // This part of Composition will see the `elevations` instance
                // when accessing LocalElevations.current
            }
t3chb0t
  • 16,340
  • 13
  • 78
  • 118

1 Answers1

1
@Composable
fun CompositionLocalExample() {
    MaterialTheme { // MaterialTheme sets ContentAlpha.high as default
        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()
                }
            }
        }
    }
}

In this sample Text sets alpha in source code using LocalContentAlpha.current value with

val localContentColor = LocalContentColor.current
val localContentAlpha = LocalContentAlpha.current
val overrideColorOrUnspecified: Color = if (color.isSpecified) {
    color
} else if (style.color.isSpecified) {
    style.color
} else {
    localContentColor.copy(localContentAlpha)
}

because of that changing value LocalContentAlpha provides changes the alpha Text gets.

As in third example where elevation is set with

Consuming the CompositionLocal CompositionLocal.current returns the value provided by the nearest CompositionLocalProvider that provides a value to that CompositionLocal:

@Composable
fun SomeComposable() {
    // Access the globally defined LocalElevations variable to get the
    // current Elevations in this part of the Composition
    Card(elevation = LocalElevations.current.card) {
        // Content
    }
}

Also for instance with Icon which has default tint

tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)

You can apply LocalContentAlpha or LocalContentColor to change default tint value or explicitly change param itself.

@Preview
@Composable
private fun Test() {

    Column(
        Modifier
            .fillMaxSize()
            .padding(10.dp)
    ) {

        // Default icon
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null
        )
        Spacer(modifier = Modifier.height(10.dp))

        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null,
            tint = Color.Green
        )
        Spacer(modifier = Modifier.height(10.dp))

        Icon(
            modifier = Modifier.alpha(.3f),
            imageVector = Icons.Default.Favorite,
            tint = Color.Green,
            contentDescription = null
        )
        Spacer(modifier = Modifier.height(10.dp))

        // Icon uses tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        // changing it also changes default tint
        CompositionLocalProvider(LocalContentColor provides Color.Green) {
            Icon(
                imageVector = Icons.Default.Favorite,
                contentDescription = null
            )
        }
        Spacer(modifier = Modifier.height(10.dp))

        CompositionLocalProvider(LocalContentAlpha provides .3f) {
            CompositionLocalProvider(LocalContentColor provides Color.Green) {
                Icon(
                    imageVector = Icons.Default.Favorite,
                    contentDescription = null
                )
            }
        }
    }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Oh! So the magic lies in the source code already using some the general `LocalX` providers and when I want to change something else then I need to do this myself and it won't be autowired and there's no way of achieving this. Would that be correct? – t3chb0t Sep 01 '23 at 08:54
  • 1
    If that Composable reads any LocalContent value you can change it using `CompositionLocalProvider ` otherwise you do it via modifiers or other params. – Thracian Sep 01 '23 at 09:15
  • 1
    If a default Composable doesn't have way to access `LocalX` you defined, you can provide any Composable with elevation as in that example, `SomeComposable `, then anywhere you use `CompositionLocalProvider` will be using elevation assigned in that provider – Thracian Sep 01 '23 at 09:20