Hi guys I have trouble with jetpack compose recomp...
# compose
t
Hi guys I have trouble with jetpack compose recomposition. The detail in the thread. Please help šŸ„²
I have a code for the home screen and numpad component (in the attachments)
Anytime I click on a button, the value of button will be fired in stateflow viewModel.getNumber(). I expect that the Text element in center screen should be recomposed. Itā€™s worked well. But I notice all buttons in the numpad is recomposed, too.
I have no idea why it happens and how to prevent it. Thanks in advance.
s
buttons: List<String>
is marked as
unstable
, maybe consider checking this out https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8 specifically in the tl;dr version, the point that reads: ā€œCollection classes like List, Set and Map are always determined unstable as it is not guaranteed they are immutable. You can use Kotlinx immutable collections instead or annotate your classes as @Immutable or @Stable.ā€ Then as an easy step to see if this is your issue, use https://github.com/Kotlin/kotlinx.collections.immutable and mark the first parameter of
NumPad
as
buttons: ImmutableList<String>
and check if that helps or not.
Right now as I see it, when HomeScreenContent gets a new
inputText
, its body recomposes, then at the bottom when
NumPad
is called, you run this
map + "Delete" + ... +
on every recomposition, and then when the
NumPad
composable is run, it canā€™t infer if that
List<String>
is the same as it was before, so it recomposes everything. Now I am not sure if what I am saying is not exactly the problem, and if it has something to do with the
LazyVerticalGrid
itself though and you need to provide a
key
to all the items, but Iā€™d say probably no since it by default should use the index of the item as a key. So Iā€™d start with the ImmutableList first.
t
@Stylianos Gakis sadly I tried both solutions but it does not work at all :(
j
One question - is that an real issue? Does this impact performance in any visible way? Since recomposition tracking is not that easy this should be the first question to ask yourself - is that really a problem?
Another question - why use LazyGrid for layout which has only 12 items?
Lazy layouts are there for lists that are not fitting on the screen, the only savings are on items that are invisible (thus not rendering). So you have no benefits from Lazy layout and all the complexity of it
Try this out using Rows and Columns and then check the issue persists
t
I am developing an app for very weak device (only 1gb ram) so I want to know how to optimize into every piece
And I used lazy layout just for learning purposes. I will use row and column and check for this again. šŸ¤Ŗ
j
Well that doesn't really answer the question - this could (and probably is) just fine 60 fps on the very weak device, just try it with release and minimized build
s
All this is good to consider, but I assumed here we were just discussing understanding how to minimize recompositions for the sake of learning. Could you show me how you did the
ImmutableList
change in code? I am fairly certain that should be enough to avoid recomposition there.
Did you pass it using
persistentListOf(*((1..9).map(Int::toString) + "Delete" + "0" + "Clear").toTypedArray()),
and changing the NumPad signature to
Copy code
@Composable
fun NumPad(
    buttons: ImmutableList<String>,
    onInputTextChange: (String) -> Unit,
)
?
j
i do agree that investigating this might be really fun and informative šŸ™‚
t
@Stylianos Gakis I change the code like this. Is this do the same with toTypedArray()?
@Jakub Syty yeah sure I pay attention to optimizing the release build, too. But for now, I just want to figure out the reason for this case šŸ˜
and I have small question, what * inside
persistentListOf(*((1..9)
mean? @Stylianos Gakis
s
Oh no, that was just to turn a
List
into an
Array
and then using the spread operator
*
to pass it to the varargs of
persistentListOf
. Read about it here https://kotlinlang.org/docs/functions.html#variable-number-of-arguments-varargs or google ā€œKotlin spread operatorā€. But yeah what you did should be correct, the NumPad() composable should just skip completely (can even inline
numPadButtons
, there isnā€™t really a reason for it to be a
val
as you have it there). Did you run this and did it still recompose unnecessarily? Also no need to ping people who are already in the thread, I get a notification for this regardless.
t
Yes, it still recomposes unnecessarily
Hi guys I figure out the root cause. It is
inputChar: ((String) -> Unit)? = null
. Because this callback refers to the function inside ViewModel, but the ViewModel is not stable, the compiler treats this lambda as not stable. So if I
remember
this lambda, the view will not be recomposed unnecessarily.
j
i don't think the reason is that ViewModel in unstable, but that all lambdas are unstable by default (because how could these not be). Can you try to do
onInputTextChange = viewModel::setNumber
?
this way you are not recreating lambda every recomposition
t
I did it before but it doesnā€™t change anything. Numpad still is recomposed.
j
you would have to pass is by reference to NumPad too
NumPad(parameters, onInputTextChange = onInputTextChange)
t
Thank you it works like a charm. When I remember the lambda or pass the lambda to a composable function. It always increases memory use when I stress-click on the Numpad. But now I pass the call back by reference, itā€™s not recomposed and does not increase memory use.
So the root cause is lambda is not stable. The buttons list was not affected in this case.
s
If you change it back to List<String>, does it work without recomposing then?
j
at some point i always tried to pass functions from view model as reference just to avoid issues like that
t
@Stylianos Gakis it works without recomposing
s
As I said above, donā€™t ping people again if theyā€™re already in the discussion, I will see this message regardless. You can simply write my name if you want to refer to me. and thatā€™s interesting, I thought List is always marked as unstable, not sure I understand whatā€™s going on in this case then.
j
I think LazyLayout might not care about the list itself
s
Do you mind sharing your ViewModel too? Iā€™d like to test it myself too.
j
since it relies on item position
t
ah sorry. I just want to reply but dont know how
about the view model, just it. I save the input number to the database (for learning purposes) and return a flow to view.
s
Just write your message, maybe adding the personā€™s name just without the ā€˜@ā€™ before it. Itā€™s not about LazyLayout, the NumPad function, if taking an unstable parameter, should always recompose if the scope of HomeScreenContent is also recomposing, which it should since its parameter inputText changes. It shouldnā€™t skip it since it canā€™t guarantee that
numbers
is stable if itā€™s a
List
. Just tested it myself in a compose for desktop project and you are right that passing the VM functions in a lambda was forcing recomposition here, since it was capturing ViewModel which is in fact unstable, however when I fix that and use lambda references to the functions, and I turn it into
List<String>
it also recomposes due to what I said above, so I had to pass VM functions by reference and also use ImmutableList, are you sure you tested without the ImmutableList and it still did not recompose?
t
Yes. I tested it so many times and it still did not recompose. I think in my case I used the default parameter for Numpad so the compiler treats the list as stable (?!)
j
Yea, compiler does this optimization
t
Yeah when I do not use the button list default parameter for NumPad it will be recomposed
j
i mean - compose compiler treats default value as non changed if nothing new is provided
it's really complex compiler plugin
pure magic
t
I have learned many things here thank you guys for your support šŸ«”
j
If you like learning about internals then i highly recommend https://jorgecastillo.dev/book/
it's really great šŸ™‚
t
thanks šŸ˜‡
z
Just wanted to chime in on an earlier comment - simply using lazy lists instead of their eager counterparts is not ā€œoptimizationā€. Lazy lists are not free, they actually have some overhead for the extra work they do to support all the lazy features. That overhead is only worth it if it is less than the cost of eagerly composing a large list. But for small lists, where nearly everything is likely to be on the screen at once, it can be more expensive than just using row or column.
t
Thank you. I got this point. I just wanna use it for learning purposes. In a real project, I will use rows and columns for this case.