https://kotlinlang.org logo
#compose
Title
# compose
m

Mark

02/10/2023, 3:39 AM
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

curioustechizen

02/10/2023, 4:17 AM
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

Mark

02/10/2023, 4:19 AM
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

mattinger

02/10/2023, 5:34 AM
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

Mark

02/10/2023, 5:42 AM
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

mattinger

02/10/2023, 5:43 AM
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

Mark

02/10/2023, 5:43 AM
Okay, that’s a stronger argument
m

mattinger

02/10/2023, 5:43 AM
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

ephemient

02/10/2023, 5:56 AM
androidx.compose.material.Button
has
onClick: () -> Unit
as the first parameter
m

Mark

02/10/2023, 5:57 AM
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

ephemient

02/10/2023, 6:01 AM
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

Mark

02/10/2023, 6:08 AM
But I’m really only talking about composable non-layouts (leaf nodes?). Whatever the terminology is.
e

ephemient

02/10/2023, 6:10 AM
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

Mark

02/10/2023, 8:01 AM
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

Filip Wiesner

02/10/2023, 1:51 PM
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

Mark

02/10/2023, 1:52 PM
Makes sense but it’s interesting that
compose-lints
allows for any trailing lambda, not just composable ones.
f

Filip Wiesner

02/10/2023, 1:57 PM
Maybe it's hard to write lint check for annotation of function parameter 😄 I don't know
272 Views