https://kotlinlang.org logo
#compose
Title
# compose
b

Billy Newman

12/15/2021, 3:54 PM
Hello all! Is it possible to get attributes, say for instance the background color, of composable that is passed as a parameter? See reply for example.
Something like this:
Copy code
@Composable
fun Content(someComposable: (@Composable () -> Unit)? = null) {
  val background = someComposable?.background()  // This does not work
}
k

Kirill Grouchnikov

12/15/2021, 4:14 PM
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.
b

Billy Newman

12/15/2021, 4:17 PM
Ok makes sense. Ideas on another to achieve what I am after? Looking into a custom scope for that composable. Still not 100% sure how to implement, but something like this:
Copy code
interface CustomScope {
   
   @Stable
   fun Modifier.background(background: Color): Modifier

}

@Composable
fun Content(someComposable: (@Composable CustomScope.() -> Unit)? = null) {
...
}
a

Adam Powell

12/15/2021, 4:19 PM
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
b

Billy Newman

12/15/2021, 4:43 PM
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:
Copy code
@Composable
fun CommonContent(header: (@Composable () -> Unit)? = null) {
  DragHandle()

  header?.invoke()
}
Copy code
@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.
c

Casey Brooks

12/15/2021, 4:57 PM
Since your custom header is a child of
CommonContent
, you can use CompositionLocals to implicitly pass parameters down through the composition tree https://developer.android.com/jetpack/compose/compositionlocal
1
c

Chris Sinco [G]

12/15/2021, 5:04 PM
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
b

Billy Newman

12/15/2021, 5:11 PM
Sure I can always pass more information in the CommonContent composable…
Copy code
@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”
Also @Adam Powell looking at implementations of customs scopes like Row and Column, they end up using:
Copy code
interface ParentDataModifier : Modifier.Element {
    /**
     * Provides a parentData, given the [parentData] already provided through the modifier's chain.
     */
    fun Density.modifyParentData(parentData: Any?): Any?
}
To allow the child to modify something on the parent right? Seems like exactly what I am trying to do.
c

Chris Sinco [G]

12/15/2021, 5:17 PM
Not really sure I want to pass parameters “down”
Right, this is where CompositionLocals can be useful as it passes things down in the hierarchy, and it works particularly well for color overrides at certain parts of a UI hierarchy
c

Casey Brooks

12/15/2021, 5:20 PM
It would look something like this:
Copy code
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
✔️ 1
b

Billy Newman

12/15/2021, 6:32 PM
Casey, thanks for the example, still new to compose. I will give this a go, I greatly appreciate the help!
c

Chris Sinco [G]

12/15/2021, 6:38 PM
+1 great example Casey!
🙏 1
a

Adam Powell

12/15/2021, 6:45 PM
the ParentDataModifiers and scoped modifier factories are less about modifying something on the parent and more about the parent passing down the capability for child elements to annotate the layout nodes they emit in a particular way