What’s the best API practice for having a composab...
# compose
j
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:
Copy code
@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.
z
Have you seen this? I havent used it myself in practice, but Ive seen it used in the material library to seemingly do what youre looking for 🙂
Copy code
Modifier.defaultMinSize()
a
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
j
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:
Copy code
@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)
?
a
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
j
But it works even the other way around (e.g. if I pass in
Modifier.size(12.dp)
) In this case we’d have:
24.coerceIn(12, 12) = ?
But nevertheless
SomethingNice
it is displayed with 12dp size
Wait a sec… so by “the upstream parent” you mean “the upstream modifier” OR the “enclosing (i.e. outer) composable”?
Gosh I’m confused. 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?
In the end swapping
modifier = modifier.size(24.dp)
with
modifier = Modifier.size(24.dp).then(modifier)
Solve any issues with sizing I had 🤷
s
Maybe I’m not understanding the question, but can’t you can solve this with default argument for modifier?
j
Nope, because a user may wish to add any modifier other than the size one while still keeping the default size
a
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
👍 1
the layout process is one where each parent (and modifier) tells the next link in the chain, "you must be between
min
and
max
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:
Copy code
@Composable fun DefaultSizeBox(
  modifier: Modifier = Modifier,
  content: @Composable BoxScope.() -> Unit
) = Box(modifier.size(24.dp), content = content)
and then call it like this:
Copy code
DefaultSizeBox(Modifier.size(64.dp)) {
  Text("Hello, world")
}
then the final modifier for the
Box
created by
DefaultSizeBox
is
Modifier.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)
👍 1
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 the
DefaultSizeBox
call thereby overrode the requested default defined in `DefaultSizeBox`'s implementation.
j
Thanks for the thorough explanation Adam, it took a while but I eventually digested all of this 🙂
👍 1
r
Hey , I goy this far. 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)
But what happened in this case? 24.coerceIn(12, 12) = ?
j
= 12
r
Ops. I know what coerceIn function does actually It was a stupid question As I see you mentioned you were getting 12. Anyway thanks