Is there a reason why a custom `Modifier.myModifie...
# compose
r
Is there a reason why a custom
Modifier.myModifier()
would be invoked on recomposition when it takes parameters, but not when no parameters are taken?
Copy code
fun Modifier.layoutWithParam(param: String) = layout { measurable, constraints ->
    println("layoutWithParam: $param") // invoked frequently

    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
        placeable.place(0,0)
    }
}
This is the custom modifier in question, which is invoked whenever the composable recomposes. Whereas this modifier is only invoked once:
Copy code
fun Modifier.layoutNoParam() = layout { measurable, constraints ->
    println("layoutNoParam") // invoked only once

    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
        placeable.place(0,0)
    }
}
This behaviour feels unexpected, especially when the parameter is a static value, e.g.
Modifier.layoutWithParam("foo")
z
When the factory function has a parameter and you capture that parameter in the layout lambda, kotlin will always allocate a new lambda instance to capture the value. When there’s no parameter, kotlin will statically allocate the layout lambda. Since the lambda instance is used to create the measure policy for the layout node, whenever a completely different instance of the lambda/measure policy is passed, it invalidates layout for that node. Lamba/function instances are only comparable by reference/identity, so every new instance will invalidate layout. This is standard kotlin behavior, has nothing to do with compose. There’s no
@Composable
code in your modifier so the compose compiler plugin doesn’t touch it.
today i learned 2
To avoid these invalidations, make a ModifierNodeElement/Modifier.Node pair. Your MNE can then define what equality means between different instances and skip even updating the modifier node. Or you can do more complex invalidation logic in the node.
today i learned 2
r
Oh this is super interesting, thank you Zach. Expanding my world of knowledge here. As an aside, for compose use-cases, this feels like a performance trap that would be very easy to accidentally fall into without realising, and definitely has lead to some head scratching on my end. For example, it's a trigger for this bug I filed yesterday which was extremely difficult to isolate because it went away based on commenting in/out logging code. To better understand this, is there any specific specs/documentation that might be worth looking at?
z
I don’t know of any that cover the whole issue, since it’s a combination of kotlin behavior and compose layout invalidation behavior. Maybe the kotlin docs on lambdas for the former? I’m not sure what docs would cover the latter. If it’s not in the docs of the layout modifier itself it probably should be.
I’m surprised this caused such a significant performance impact though. Layout in compose is aggressively skipped up and down the tree when an invalidation in one node doesn’t actually change anything. So typically over-invalidating a single layout node shouldn’t really have that much impact, especially if you’re already recomposing anyway.