One Off the Slack: How Do We Examine the Content of Child Composables?

TL;DR: You don’t.

Billy Newman asked:

Is it possible to get attributes, say for instance the background color, of composable that is passed as a parameter? Something like this:

@Composable
fun Content(someComposable: (@Composable () -> Unit)? = null) {
  val background = someComposable?.background()  // This does not work
}

Kiril Grouchnikov was, um, grouchy:

There is no such thing. This someComposable is not a view / container / component in the traditional sense. It may emit one UI component, it may emit more than one, or it may not emit anything.

Google’s Adam Powell asked:

what exactly is it that you’re after? The compose way is generally to tell your children what to do and expect them to do it, not assemble your children and then query their state after the fact

it’s highly unusual for a parent to ask a child about the nature of its background drawing and wanting to do that is a bit of an encapsulation break, like using reflection to inspect the private properties of an object

IOW, Billy’s question suggested an XY problem.

Billy elaborated:

Sure, specifically I have a common composable for a draggable view. That draggable view has a “drag handle view” at the top. However that common view can also be customized with a header:

@Composable
fun CommonContent(header: (@Composable () -> Unit)? = null) {
  DragHandle()

  header?.invoke()
}
@Composable
fun CustomContent() {

  CommonContent() {
    // custom header
    Column(Modifier.background(Color.Blue)) { ... }
  }
}

I would like for the drag handle view to have the same background color as the custom header. And I don’t really want to duplicate code. IE in this case a drag handle is “common” and should always be present regardless of the custom header.

Casey Brooks recommended CompositionLocal:

Since your custom header is a child of CommonContent, you can use CompositionLocals to implicitly pass parameters down through the composition tree

Google’s Chris Sinco echoed that recommendation:

Adding to Casey’s suggestion, using colors from MaterialTheme like MaterialTheme.colors.background can be useful here because from the code POV you are using a semantic color key, but can use CompositionLocal to override in specific parts in the hierarchy if needed.

You could also consider a color parameter for DragHandle that defaults to something

Billy was unhappy with the parameter option:

Sure I can always pass more information in the CommonContent composable…

@Composable
fun CommonContent(
  header: (@Composable () -> Unit)? = null,
  headerColor: Color = Color.White
) {
  DragHandle(headerColor)

  header?.invoke()
}

But where does that end. Passing in every attribute the drag handle would have in common with the header component seems excessive.

Not sure I fully understand how to use CompositionLocals in this case. Not really sure I want to pass parameters “down”

Casey then supplied an example:

val LocalHeaderColor = compositionLocalOf { Color.Unspecified }

@Composable
fun CommonContent(
  header: (@Composable () -> Unit)? = null,
  headerColor: Color = Color.White
) {
    CompositionLocalProvider(LocalHeaderColor provides headerColor) {
        DragHandle()
        header?.invoke()
    }
}

The content of both DragHandle and the header lambda can then reference the configured header color with LocalHeaderColor.current, without having to pass it explicitly through function parameters

Google’s Jim Sproch did not happen to chime in on this thread. My guess is that Jim would have not been a fan of the compositionLocalOf() approach, in that it hampers reuse and testability.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!