I'm building a composable like:
@Composable
fun BottomSectionWrapper(
content: @Composable () -> Unit,
bottomSection: @Composable () -> Unit,
)
I now would like to know if the parameter bottomSection
is empty, so i can adjust my layout accordingly. Is this possible?
I won't be defining "empty" any further; i'll take whatever is technically possible:
Let me explain my typical usecase: I'm using this wrapper to attach buttons to the lower end of a screen. And if there is a button, i want to add a fade.
What have i tried?
I tried making the lambda nullable - that did not compile
BottomSectionWrapper(
content = { /* ... */ },
bottomSection = if(showDeleteButton){
Button { Text("Delete") }
} else null
)
Also: Intuitively, i would have written this like this (which would not have been detected by a simple null check):
BottomSectionWrapper(
content = { /* ... */ },
bottomSection = {
if (showDeleteButton) {
Button { Text("Delete") }
}
}
)
If you wish to have nullable contents you need to set declaration as
@Composable
fun BottomSectionWrapper(
modifier: Modifier = Modifier,
content: @Composable (() -> Unit)? = null,
bottomSection: @Composable (() -> Unit)? = null,
) {
Column(modifier = modifier) {
content?.invoke()
bottomSection?.invoke()
}
}
And you can use it as in this demo
@Preview
@Composable
private fun BottomSectionWrapperTest() {
Column {
var isSet by remember {
mutableStateOf(false)
}
Button(onClick = { isSet = isSet.not() }) {
Text("isSet: $isSet")
}
val bottomSection: (@Composable () -> Unit)? = if (isSet) {
{
Box(modifier = Modifier.size(100.dp).background(Color.Green)) {
Text("Bottom Section")
}
}
} else null
BottomSectionWrapper(
modifier = Modifier.border(2.dp, Color.Red),
content = {
Text("Content Section")
},
bottomSection = bottomSection
)
}
}
If you wish to detect whether a Composable is empty or have 0.dp size or any size you can use Layout
with different approaches, dimension setting and placing options.
Second one with Modifier.layoutId
is used in default Composables such as TextField
, Tab
@Composable
fun BottomSectionWrapperLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit = {},
bottomSection: @Composable () -> Unit = {},
) {
Layout(
modifier = modifier,
contents = listOf(content, bottomSection)
) { measurables: List<List<Measurable>>, constraints: Constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val contentPlaceable: Placeable? =
measurables.first().firstOrNull()?.measure(looseConstraints)
val bottomSectionPlaceable: Placeable? =
measurables.last().firstOrNull()?.measure(looseConstraints)
println("content: $contentPlaceable, bottomSectionPlaceable: $bottomSectionPlaceable")
// you can set width and height as required, i make it as a Column
// this width and height determines whether it's a Column, Row or a Box
val contentWidth = contentPlaceable?.width ?: 0
val contentHeight = contentPlaceable?.height ?: 0
val bottomSectionWidth = bottomSectionPlaceable?.width ?: 0
val bottomSectionHeight = bottomSectionPlaceable?.height ?: 0
val totalWidth = if (constraints.hasFixedWidth && constraints.hasBoundedWidth)
constraints.maxWidth else contentWidth.coerceAtLeast(bottomSectionWidth)
.coerceIn(constraints.minWidth, constraints.maxWidth)
val totalHeight = if (constraints.hasFixedHeight && constraints.hasBoundedHeight)
constraints.maxHeight else (contentHeight + bottomSectionHeight)
.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(totalWidth, totalHeight) {
// You can place them according to your logic here
contentPlaceable?.placeRelative(0, 0)
bottomSectionPlaceable?.placeRelative(0, contentHeight)
}
}
}
With this Layout
you can detect whether any of them are empty by checking if Placeable is null which means empty lambda is passed and if not null you can check its size.
@Preview
@Composable
private fun LayoutTest() {
Column(modifier = Modifier.fillMaxSize()) {
BottomSectionWrapperLayout(
modifier = Modifier.border(2.dp, Color.Red),
content = {
Text("Content Section")
},
bottomSection = {
Text("Bottom Section")
}
)
Spacer(Modifier.height(20.dp))
BottomSectionWrapperLayout(
modifier = Modifier.border(2.dp, Color.Red),
bottomSection = {
Text("Bottom Section")
}
)
Spacer(Modifier.height(20.dp))
BottomSectionWrapperLayout(
modifier = Modifier.border(2.dp, Color.Red),
content = {
Text("Content Section")
}
)
}
}
If you check out println you can see that in each case you will be able to see what's null or not. Also you can get Placable dimensions as well.
Other option is to create this layout as Textfield does in line 495.
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=495?q=TextField&ss=androidx%2Fplatform%2Fframeworks%2Fsupport
But with this approach you won't be able to check if you passed empty lambda because its placed inside a Box when not null. However this approach is often used since you can check and compare contents by their ids instead of checking lists.
@Composable
fun BottomSectionWrapperLayout2(
modifier: Modifier = Modifier,
content: @Composable (() -> Unit)? = null,
bottomSection: @Composable (() -> Unit)? = null,
) {
Layout(
modifier = modifier,
content = {
if (content != null) {
Box(
modifier = Modifier.layoutId("content")
) {
content()
}
}
if (bottomSection != null) {
Box(
modifier = Modifier.layoutId("bottomSection")
) {
bottomSection()
}
}
}
) { measurables: List<Measurable>, constraints: Constraints ->
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
val contentPlaceable: Placeable? =
measurables.find { it.layoutId == "content" }?.measure(looseConstraints)
val bottomSectionPlaceable =
measurables.find { it.layoutId == "bottomSection" }?.measure(looseConstraints)
// you can set width and height as required, i make it as a Column
// this width and height determines whether it's a Column, Row or a Box
val contentWidth = contentPlaceable?.width ?: 0
val bottomSectionWidth = bottomSectionPlaceable?.width ?: 0
val contentHeight = contentPlaceable?.height ?: 0
val bottomSectionHeight = bottomSectionPlaceable?.height ?: 0
val totalWidth = if (constraints.hasFixedWidth && constraints.hasBoundedWidth)
constraints.maxWidth else contentWidth.coerceAtLeast(bottomSectionWidth)
.coerceIn(constraints.minWidth, constraints.maxWidth)
val totalHeight = if (constraints.hasFixedHeight && constraints.hasBoundedHeight)
constraints.maxHeight else (contentHeight + bottomSectionHeight)
.coerceIn(constraints.minHeight, constraints.maxHeight)
layout(totalWidth, totalHeight) {
contentPlaceable?.placeRelative(0, 0)
bottomSectionPlaceable?.placeRelative(0, contentHeight)
}
}
}
Usage is same as other layout.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With