Does anyone know if there are plans to make `SubCo...
# compose
b
Does anyone know if there are plans to make
SubComposeLayout
and
Intrinsics
work together? For example something like the following:
Copy code
Row(modifier = Modifier.fillMaxWidth.height(IntrinsicSize.Min)) {
    BoxWithConstraints(...) {
        ...
    }
}
this currently crashes with an IllegalStateException with a message of
Intrinsic measurements are not currently supported by SubcomposeLayout
Any way to “fix” this?
z
Have you considered just making a custom layout instead?
b
hmm, I had not. I had not. In my particular use case, I could eliminate the
Row
and use a
ConstraintLayout
and then I wouldn’t need to use Intrinsics for the height. I posted the question for 2 reasons: 1. To find out if if this is something that won’t (or can’t) ever be “fixed”, so I need to learn other tools to deal with situations like this, and 2. What those other tools might be. But yeah, I custom layout might be one of those tools.
z
Can you post more code?
b
@Zach Klippenstein (he/him) [MOD] sorry, got sidetracked yesterday. Sure I can post more code. The TL:DR is, I’m using
BoxWithConstraints
to set the fontSize on a
Text
dynamically based on the Box’s width (it’s a reusable component that can by any size). If I could figure out a (decent) way to do that without
SubComposeLayout
, it would solve my problems. The other option is to figure out the best way to NOT use Intrinsics for the container’s height. See below for the details on the container.
So, first off, I have a
Row
(it’s an item in a Lazy Column). It has several things, but there are 2 that matter. There is a
Column
in the Row that contains three
Text
composables, and essentially the `Row`’s height should wrap the Column. Next to the column is a
SaddleCloth
composable, which is a wrapper for a
BoxWithConstraints
. The
SaddleCloth
composable is a reusable component that can be any size, and uses the
BoxWithConstraints
to set the fontSize on the
Text
it contains dynamically, based on it’s width. it also has a pretty complex background that is drawn from a
Path
(to create things like stripes and triangles).
Copy code
@Composable
fun RunnerRow(runner: Runner, race: Race) {

    Row(modifier = Modifier
        .fillMaxWidth()
        .height(IntrinsicSize.Min)
        .padding(end = 8.dp)) {

        SaddleCloth(
            programNumber = runner.programNumber,
            trackType = race.raceId.trackId.trackType,
            bettingInterest = runner.bettingInterest,
            modifier = Modifier.width(24.dp).fillMaxHeight()
        )

        Column(
            modifier = Modifier
                .weight(1F)
                .padding(horizontal = 8.dp, vertical = 6.dp)
        ) {
            Text(text = runner.name, fontWeight = FontWeight.Bold)
            Text(text = runner.jockey, style = MaterialTheme.typography.caption)
            Text(text = runner.trainer, style = MaterialTheme.typography.caption)
        }
    }
}
And as I mentioned, the
SaddleCloth
wraps a
BoxWithConstraints
and sets the fontSize based on it’s width (which is not always fixed. The composable is used in several places with varying sizes).. In the above
Row
, I want the
SaddleCloth
to fill the height of the
Row
(which should be determined by wrapping the Column of 3 Text composables). Using Intrinsics works great for that, except that the SaddleCloth is a BoxWithConstraints, which uses SubComposeLayout.
Copy code
@Composable
fun SaddleCloth(programNumber: String, trackType: TrackType, bettingInterest: Int, modifier: Modifier = Modifier) {
    val saddleCloth = remember(trackType, bettingInterest) { saddleCloths.forRunner(trackType, bettingInterest) }

    val boxModifier = when {
        modifier.any { it is LayoutModifier } -> modifier
        else -> modifier.fillMaxSize()
    }

    BoxWithConstraints(
        modifier = boxModifier
            .drawWithCache {
                // Draw a background using vector [Path]s based on the saddlecloth type.
                onDrawBehind {
                    when (saddleCloth.type) {
                        is SaddleClothType.Solid -> drawRect(color = saddleCloth.type.primaryColor)
                        is SaddleClothType.Triangles -> drawTriangles(
                            size = size,
                            topColor = saddleCloth.type.primaryColor,
                            bottomColor = saddleCloth.type.bottomColor)
                        is SaddleClothType.VerticalStripes -> drawVerticalStripes(
                            size = size,
                            baseColor = saddleCloth.type.primaryColor,
                            stripeColor = saddleCloth.type.stripeColor)
                        is SaddleClothType.HorizontalStripes -> drawHorizontalStripes(
                            size = size,
                            baseColor = saddleCloth.type.primaryColor,
                            stripeColor = saddleCloth.type.stripeColor)
                    }
                }
            },
        contentAlignment = Alignment.Center
    ) {
        val fontSize = maxWidth * 0.4f

        Text(
            text = programNumber,
            textAlign = TextAlign.Center,
            fontSize = LocalDensity.current.run { fontSize.toSp() },
            fontWeight = FontWeight.Bold,
            color = saddleCloth.contentColor)
    }
}
The
SaddleCloth
composable renders like this:
And the overall look I’m going for is:
z
Ok, I think I understand. Since you don’t need the intrinsic height of the saddlecloth component to calculate height, I think you could probably wrap it with something that would just provide a meaningless intrinsic height of zero so the row effectively ignores it.
Alternatively, a relatively simple custom layout could just measure the column first and then use the measured size to provide tight constraints to the saddlecloth (measurement doesn’t need to happen in the same order as composition).
b
so for the first suggestion… I think you are saying that basically you could “trick” the row into thinking that the SaddleCloth is 0 height, so then the Row can use the
wrapContentHeight
modifier, and then the SaddleCloth is fine to use
SubComposeLayout
(since there are no more intrinsics) and can
fillMaxHeight
and it’ll get measured and composed as shown above … do I have that right?
z
Not quite - by explicitly reporting a zero intrinsic height, the row won’t use the SC’s height when calculating its own height. Then you can still use fillMaxHeight on the SC itself to match the intrinsic height of the column of texts.
In terms of passes, intrinsic height is basically a super cheap, pre-calculated constraint that a layout can query before measuring. That’s useful because measurement requires passing constraints in, so intrinsic sizes allows the row to figure out what the constraints should be for all its children by asking each of them how big they want to be first. So even if you report a zero intrinsic height you should still be able to measure yourself at whatever height is appropriate- including the max height your layout has passed you in the constraints. Anyway, I haven’t tried it, but I think that should work based on my understanding of intrinsics. Even if it does though, I think the custom layout might actually be less hacky and easier to read because you can make the layout intent crystal clear without leaving your code readers to figure out how the intrinsics all work together.
b
Yeah I like the custom layout idea too.
I’m still a little confused on the first part …
by explicitly reporting a zero intrinsic height
how would I do that?
z
you might actually need to implement a custom (child) layout or layout modifier to do it. so that would really obscure your intent, and make an even greater argument for just using a custom layout for the row
b
ah ok. I kept looking for
IntrinsicSize.ZERO
or something … thought I was going crazy for a minute there 🙂
z
yea, the whole intrinsics approach is super hacky and i feel worse about even suggesting it the more i think about it 😅 I’m not super familiar with all the intrinsics APIs, but i’m guessing if there’s no convenient way to do this it’s because it’s just better to use a custom layout anyway
b
Yeah proabably so. And it would give me a good chance to actually internalize the concepts behind custom layouts by building something useful of my own. To this point I’ve done the codelabs and what not where they teach you the custom layout basics, but I haven’t really explored it yet to get a really good grasp on it.