When writing a composable that accepts a mandatory...
# compose
m
When writing a composable that accepts a mandatory click handler, I’d prefer to put that as the last argument, but this violates the recommendation to put a
Modifier
arg after all args with no defaults. What’s the reasoning here, and am I just resigned to shifting the click handler arg to be before the modifier arg?
c
I prefer to reserve the trailing lambda argument for a Composable lambda. A click handler is not Composable so I place it before the modifier.
m
In my particular use case, I have a composable for a Dialog Button, and there really isn’t much to configure other than the label and the click handler. It’s the same for any leaf node composable, I think.
Another example is a composable (emitting content) which uses the builder pattern. So the
builderAction
would be a mandatory arg but must go before
Modifier
and therefore the caller cannot use a trailing lambda for the builder arg.
m
If you're asking why that's recommended, it's because of the way kotlin resolves parameters. If you have default values for parameters and you skip one, everything after it has to be explicitly called out as a named argument. So the example below can't compile until you put the modifier parameter after the text.
Copy code
@Composable
@Preview
fun SomeComposablePreview() {
    SomeComposable("foobar")
}

@Composable
fun SomeComposable(
    modifier: Modifier = Modifier,
    text: String,
) {
    
}
Also as others have said, trailing lambas in compose are best suited for composable content. If you have a function as your last parameter, Android Studio will instinctively autocomplete your function into the trailing lambda syntax, which gets annoying. Especially if you're like me and prefer named arguments for most things.
And the parameter thing is a general kotlin recommendation, not specific to compose really.
m
I have no issue with your example above, because there is no reason to place a non-lambda arg after the optional modifier arg. I don’t really understand your last point. AS auto-completing a mandatory click handler, is a good thing IMO, just like for when you have a last arg composable lambda. It looks way better to have:
Copy code
fun FooButton("foo") {
    doSomethingOnClick(...)
}
So I understand the argument when the composable is accepting a composable content arg, but I’m talking about ones that don’t.
m
Except that when people read compose code, they expect trailing lamba blocks of a composable function to be composable code. It's about conventions.
m
Okay, that’s a stronger argument
m
That said, there are times where i don't have nested composable content, and have multiple callback functions. And i just live with it.
e
androidx.compose.material.Button
has
onClick: () -> Unit
as the first parameter
m
But that also has a content arg at the end
So I guess the content arg is an exception to general rule of arg ordering
e
https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md
Layout functions SHOULD place their primary or most common
@Composable
function parameter in the last parameter position to permit the use of Kotlin's trailing lambda syntax for that parameter.
yes
one where that isn't the case:
androidx.compose.ui.layout.Layout
has the
content: @Composable () -> Unit
in non-trailing position, but that's more consistent with the other overloads taking
contents: List<@Composable () -> Unit>
parameters in the same argument position, and a
MeasurePolicy
ends up in the final position instead, usually written with a SAM-converted trailing lambda
but I can't think of other composable layouts with trailing non-composable lambdas
m
But I’m really only talking about composable non-layouts (leaf nodes?). Whatever the terminology is.
e
any
@Composable
function returning
Unit
is called a layout, because its only purpose to be called is for the side-effect of emitting UI
hmm. that's my thinking but I guess that disagrees with the doc
those are called Elements, according to it
m
BTW, compose-lints doesn’t complain if I put a single non-null mandatory click handler
() -> Unit
as the last argument. And sure enough fits the message we get when there is an issue:
Copy code
Parameters in a composable function should be ordered following this pattern: params without defaults, modifiers, params with defaults and optionally, a trailing function that might not have a default param.
f
I agree with Matthew
Except that when people read compose code, they expect trailing lamba blocks of a composable function to be composable code. It's about conventions.
That is the main argument for me. Doesn't matter if it's leaf composable or not. I expect UI elements with trailing lambda to be "composable". I don't want to check the documentation to know if it is or isn't composable and the annotation is not visible when typing parameters.
m
Makes sense but it’s interesting that
compose-lints
allows for any trailing lambda, not just composable ones.
f
Maybe it's hard to write lint check for annotation of function parameter 😄 I don't know
325 Views