One Off the Slack: How Do We Have Default Sizes?
Marco Romano asked:
What’s the best API practice for having a composable with a default size but still allow it to be overridden via modifiers? I was thinking something like:
@Composable
fun SomethingNice(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.size(24.dp),
) {
// Something more...
}
}
It is working.
But I was afraid the “default” 24dp modifier would be called after the one passed in via parameter therefore always overriding it. Turns out this is not true. But can’t understand why.
Google’s Adam Powell replied:
Layout sizing modifiers alter the measurement constraints of their element more or less as (pseudocode)
min = max = myRequestedSize.coerceIn(parentSuppliedMin, parentSuppliedMax)
If the incoming constraint range doesn’t include the requested size then it will try to get as close as it can
But only the
requiredSize
modifiers override parent constraints
Marco was mystified:
Not sure I’m following. What I meant was:
If I draw this:
SomethingNice()
It gets drawn at 24dp as expected.
And if I draw this:
SomethingNice(modifier = Modifier.size(64.dp))
It gets drawn at 64dp. But it is strange to me as in the code it will happen something like:
@Composable
fun SomethingNice() {
Box(
modifier = Modifier.size(64.dp).size(24.dp),
) {
// Something more...
}
}
Shouldn’t the second call to
.size(24.dp)
override the first call to.size(64.dp)
?
The problem lies in size coercion rules. As Adam put it:
24.coerceIn(64, 64) = 64
24 is out of range of the constraints given by
size(64.dp)
so it’s coerced into the constraints of the upstream parent
Marco got even more confused:
Wait a sec… so by “the upstream parent” you mean “the upstream modifier” OR the “enclosing (i.e. outer) composable”?
So there’s no way to create a composable that would size itself to a default value while at the same time offering the (opt-in) possibility of sizing it to any other value?
But, later, Marco found a possible solution:
In the end swapping
modifier = modifier.size(24.dp)
withmodifier = Modifier.size(24.dp).then(modifier)
Solve any issues with sizing I had 🤷
Adam tried again to explain:
You should never need to invert the modifier order that way, you can still change the size externally with the component expressing its own default using the standard ordering.
“upstream parent” means the modifier that comes before it if there is one, or the parent layout composable if there are no more modifiers before it
the layout process is one where each parent (and modifier) tells the next link in the chain, “you must be between
min
andmax
size; how big are you?”Modifier.size
narrows those min and max constraints to be as close to the requested value as possible, within the constraints of how it was measured by its immediate parent (whether that parent is another layout modifier or a layout composable)
so if I write:
@Composable fun DefaultSizeBox(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit
) = Box(modifier.size(24.dp), content = content)
and then call it like this:
DefaultSizeBox(Modifier.size(64.dp)) {
Text("Hello, world")
}
then the final modifier for the
Box
created byDefaultSizeBox
isModifier.size(64.dp).size(24.dp)
. Measurement then proceeds; let’s say the parent of the DefaultSizeBox measures it with the constraints(min = 0, max = 500)
(simplified to only one dimension here for the sake of this discussion; both width and height each have a min and max)
That measurement operation reaches the first
.size(64.dp)
. 64 is between 0 and 500, so now the measurement proceeds, but with(min = 64, max = 64)
next, measurement reaches
.size(24.dp)
. 24 is not between 64 and 64; in order to be within that range the constraints are “changed” to(min = 64, max = 64)
. The final box is measured with those constraints, and reports that it is size 64. The “outer” modifier passed to theDefaultSizeBox
call thereby overrode the requested default defined inDefaultSizeBox
’s implementation.
Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!