Hey I have a question for the compose experts :gri...
# compose
r
Hey I have a question for the compose experts 😁 I'm having unwanted recomposition when using
clickable
or any other Modifier extension that implements
composed
under the hood, so I dived a bit into
composed
extension. From my own investigation, it seems that the root cause is that is creating a new
ComposedModifier
each time the parent scope gets invalidated, so that would mean that any Composable (i.e Text) that is implementing something like
Modifier.clickable { }
won't be skipped if the parent scope gets invalidated But then it seems that there's an open issue tracker about this: https://issuetracker.google.com/issues/241154852 In the issue tracker it states that: Returning values from Composable functions are the cause of recomposition Sadly I couldn't reproduce it by making an extension that returns value (Int, string) or even a Modifier. 🤔 The only way I was able reproduce the issue is by having a custom class that inherits from
Modifier.Element
and this is creating a instance each time
. (If it's an
object
type it doesn't recompose
) So now I'm a bit bugged, I'd like to know what's exactly causing the Composable that implements
composed
to be recomposed when the parent scope gets invalidated?
a
Have you seen this talk already?

https://youtu.be/BjGX2RftXsU

r
Nope, I haven't!! I'll watch it now, ty!
z
Im not exactly an expert on compose, but I have a ton of clickable modifiers in my codebase and Im not seeing any additional recompositions as a result. For example, if I change the value of a
State<T>
then relevant composables are recomposed once - from my understanding youre saying that they recompose 2 or more times for you? If thats the case, for me personally I was able to reduce that to 1 by making sure that the variables I pass to composables were stable (composable metrics can help with that). Hope this helps, and I think its also worth mentioning that you should probably only dig deeper if its actually causing any performance problems for you (or youre just really curious like me 😅).
r
Ok that's a lot of information in the video but I loved the talk 😄 So I think from min 5.55 to 15:15 answers my question What I got is: • You can't skip Composable functions that return value. •
composed
is not a Composable itself so the lambda can't be cached and you can't compare with the previous result ◦ And I guess that's the main thing that causes to be recomposed? • The
composed
is basically a state Modifier and the other "stateless" modifiers live alongside those ◦ Forget about
composed
,
Modifier.Node
is coming I think it's a lot for me but at least got some new information, I really wanted to know how to reproduce:
"_Composable functions that return value will be recomposed_"
Like the way I understand is: If you have a child composable function that returns a value and the parent scope gets invalidated, this will be re-run and recomposed. But for some reason there's something that i'm missing
Hey @Zoltan Demant I have a minimal example here:
Copy code
@Composable
fun MyScreen() {
    var offset by remember { mutableStateOf(0) }

    Column(modifier = Modifier.fillMaxSize()) {
        Text(text = "Dummy offset text", modifier = Modifier.offset(x = offset.dp))
        Text(text = "Click me", modifier = Modifier.clickable { offset += 10 })
        Text(text = "Not clickable, not recomposed")
        Text(text = "Cickable, but don't click me, still got recomposed :(", modifier = Modifier.clickable {  })
    }
}
When you read the
offset
in the first text and
MyScreen
composable gets invalidated, the last text is being recomposed just because it implemented
clickable
from my understanding youre saying that they recompose 2 or more times for you?
Just 1 time, along the other siblings. The last text is unused but since it implements
clickable
(
composed
under the hood
) then it gets recomposed as well
I think its also worth mentioning that you should probably only dig deeper if its actually causing any performance problems for you (or youre just really curious like me 😅)
Just curious like you 😉! I really need to understand what's going on (Or at least try) in order to have a minimum of knowledge to be able to debug later haha
If thats the case, for me personally I was able to reduce that to 1 by making sure that the variables I pass to composables were stable (composable metrics can help with that).
Thanks! I've used that when I have no idea of what's going on, sadly doesn't help in this case because is just a small composable that doesn't involve any passing state/parameter or anything like that. Just a
Text
that implements
clickable
z
Hi @robercoding 👋🏽 Im a bit torn by this; the simple answer based on my understanding is that your text is recomposed because the offset state invalidates the entire column scope - and since you are effectively creating a new
Modifier.clickable
instance on each recomposition, the text composable itself is recomposed too (due to the parameter passed to it being different; whereas the other texts just get the same exact strings). I think this is a great article covering invalidation scopes. As an example, Im fairly certain that you could skip the recomposition for the last text if you were to create a separate composable with it (effectively not passing anything to it, since the clickable lambda doesnt do anything).
r
your text is recomposed because the offset state invalidates the entire column scope
Yes, I know it's recomposed because of the offset. Don't mean to be picky but in reality it invalidates the
MyScreen
scope since
Column
is an inline function which means it's not a real scope
you are effectively creating a new
Modifier.clickable
instance on each recomposition, the text composable itself is recomposed too (due to the parameter passed to it being different;
Exactly! I know I'm creating an instance each time it recomposes. What I wanted to know exactly is why does
.composed { }
do that. (I think I already know more less what's going on based on the above video)
I think this is a great article covering invalidation scopes.
I've already read it in the past! Nevertheless thanks for the recommendation, that's probably my top 1 articles about recomposition & scopes 😉
Im fairly certain that you could skip the recomposition for the last text if you were to create a separate composable with it
Yes, but I wanted to dig up a bit on why's being recomposed and have a more "_trustable_" answer. It's not really something that I'm trying to solve but rather trying to understand why that happens Anyways, thank you! 😁