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

Kacper

03/01/2024, 12:53 PM
Hello, hope you are having a good time. I have a small question about memoizing lambdas. I have this scenario
Copy code
LazyRow(state = lazyRowState) {
    items(
        items = items,
        key = { state -> state.id }
    ) { state ->
        SomeCard(
            state = state,
            onClick = {
                    state.onClick(state.id)
            }
        )
    }
}
The items are of type
ImmutableList<SomeState>
and
state.onClick
makes it so the reducer updates one of the items to be expanded (copying and assigning). When I click on any of the cards it makes all of the cards to recompose. When going to debug mode and looking at what changes I see that
state
is unchanged but the
onClick
is changed every time for every
SomeCard
item. Adding
remember (state) { ... }
before the lambda fixes the problem, and here comes the question. Shouldn't compose compiler do it automatically? I have read that this is his default behavior in this kind of situations when the value is of stable type. Leaving the LazyRow behind and just experimenting with
forEach
loop gives the same results.
s

Stylianos Gakis

03/01/2024, 1:11 PM
The compiler will indeed be doing this https://medium.com/androiddevelopers/jetpack-compose-strong-skipping-mode-explained-cbdb2aa4b900 soon. Is your
SomeState
not a stable type? If your lambda is capturing a non stable reference, it will not be remembered automatically in the current version of compose
k

Kacper

03/01/2024, 1:13 PM
yea, I know about the unstable captures, and the worst part here is that it should be stable as the data class has only stable types inside
Copy code
data class SomeState(
    val id: String,
    val clockTime: String,
    val isExpanded: Boolean,
    val onClick: (id: String) -> Unit
)
And for the love of god I cannot reproduce it in a sample project I have for this types of questions 😅
s

Stylianos Gakis

03/01/2024, 1:21 PM
And for the love of god I cannot reproduce it in a sample project I have for this types of questions 😅
Don’t we all 😅
I am not 100% confident in how it works with lambdas in the state object itself. Where it is created, maybe that one is capturing a reference to say, the ViewModel, which is definitely not stable, and therefore you are experiencing this?
s

Sean Proctor

03/01/2024, 1:26 PM
I can't find any information about lambdas in state objects either. If you're having an issue, I'd mark the state class as
@Immutable
and see if that helps.
k

Kacper

03/01/2024, 1:33 PM
Wait a second... Putting
@Immutable
hell, even
@Stable
(which is actually the same xD) fixes the issue... Is my
SomeState
not stable?!? It has only stable types inside. What the hell... I will pull out the big guns and check compose compiler reports but thats strange as hell
s

Sean Proctor

03/01/2024, 1:34 PM
I guess lambdas aren't considered an immutable type.
Likewise if you had a
List
in there. You need to use immutable types not just
val
instead of
var
.
@Stable
means that the value can be changed, but that you will notify on changes. I don't think that is the case here.
k

Kacper

03/01/2024, 2:56 PM
Compiler metrics return
Copy code
stable class SomeState {
  stable val id: String
  stable val clockTime: String
  stable val isExpanded: Boolean
  stable val onClick: Function1<@[ParameterName(name = 'id')] String, Unit>
  <runtime stability> = Stable
}
even without annotations haha. Really strange, seams that only annotation force compose compiler to remember the lambda. Maybe its some kind of bug, or just I don't see something trivial. Who knows. I hope
strong skipping
will resolve like 90% of those issues in the future ;D
But yea, thanks guys for the help ! Even tho we didnt find the reason haha
s

Sean Proctor

03/01/2024, 2:58 PM
That's really strange. Maybe someone who understands better what's going on here can weigh in.
s

Stylianos Gakis

03/01/2024, 3:00 PM
What's your actual onClick though? What is that one capturing? From the real object that is used there?
k

Kacper

03/01/2024, 3:26 PM
Copy code
viewModelScope
dispatcherProvider
reducer
but I see that dispatcherProvider is unstable, so I got rid of it and make it pass empty function like
Copy code
private fun onClick(id: String) {
    }
to the state object. Event then when I recompose manually the parent composable the onClick reports that it changed
I suspect that the issue is somewhere in communication between modules. Moving most of the code to the sample project in single module and running it works as expected. Key point from this? Mark your states as
@Stable/@Immutable
when they are in other modules to be sure that they wont cause recomposition where they shouldn't.
s

Stylianos Gakis

03/02/2024, 12:01 PM
Oh yeah. My understanding of how it works is that the compose compiler only infers the stability of classes where it is actually applied. If you have a module which simply does not have the compose compiler and only has your presenter etc all those classes are probably just not marked as stable, even if they would be normally. You could just apply the compose compiler on that module to see if that makes any difference. Otherwise marking them manually will also work. I really wonder if my understanding of this is correct and if this will always be this way or not.
2 Views