Hi, I'm trying to use LazyColumn but I have a weir...
# compose-desktop
a
Hi, I'm trying to use LazyColumn but I have a weird problem, I have a @Composable function that takes a
channel
class which has a
message
property which is a
MutableMap<Long, Message>
then I show the list of the messages using the LazyColumn, for that I have no problem. But when I create my
channel
, I add to it 20 messages by doing this
Copy code
for (i in 0..20) {
    channel.messages.put(i, Message("test $i"))
}
But in result I get 2-3 messages only, and the numbers are random, sometimes it's 0, 5 and 14, sometimes 5 and 20, and it's very weird and I can't find a way to make it working, what am I doing wrong ? I'm using the 1.0.0-alpha4-build331 version.
h
MutableMap... Never use mutable classes with Compose. Use
mutableStateOf()
or
mutableStateMapOf()
a
How ?
z
If I'm reading correctly, you're generating messages in some background coroutine or thread then trying to send them on a coroutines
Channel
to your UI to be displayed – is that correct?
Or not,
Channel
doesn't have a
messages
property. What is
channel
?
a
channel is an instance of a class
Channel
not related to coroutines
I'm generating the messages in main()
z
Ah ok. So what Philip meant is that mutable types that aren't built to work with compose won't notify compose about updates, which will cause the bugs you noticed. Compose provides special collection types that will correctly update compositions, so you could use one of these for
messages
by creating the map with
mutableStateMapOf
instead of
mutableMapOf
. Alternatively,
messages
could be a
MutableState
object that holds an immutable map (i.e.
mapOf
), where you're sending a new map in every time you want to add a message.
👍 1
a
Oh okay I see
I'll try thanks !
a
I still have the problem using
mutableSteMapOf
:(
But now the messages are not even in order
z
Copy code
var message by remember { mutableStateOf(Message("$text 0", user)) }
	
	for (i in 0..20) {
		message = Message("$text $i", user)
		channel.messages[message.id] = message
	}
This is a bit smelly – creating a mutable state, then mutating that state multiple times in the same composable in a loop. It looks like
message
isn't actually state at all and shouldn't be a mutable state or remembered. Is that the only place where you're actually adding items to the message list?
Copy code
interface ITextChannel {
	val messages: SnapshotStateMap<Snowflake, Message>
Not related to correctness, but the fact that messages is a
SnapshotStateMap
is an implementation detail, I would just make this interface property type
MutableMap
instead
a
okay thanks I'll fix these and retry
yes it's the only place @Zach Klippenstein (he/him) [MOD]
z
I think some of those state issues when seeding the list might be doing weird things. If it still happens after changing that code happy to take another look
a
I still have the problem
very weird numbers
z
So another issue is that every time that
App
composable recomposes, you're gonna add another 30 items to the list, but that doesn't explain the ordering…
a
oh yes you're right
z
wait, this is a map. I thought mutableStateMapOf preserved insertion order, so i would think the ordering would match iteration order. But since order is significant, and it is semantically an ordered list of items, it would probably be better to use a list instead (i.e.
mutableStateListOf
)
I'm not sure about that insertion order guarantee actually, i don't see it in the docs anywhere. That might be the cause.
a
the order is not very important I guess, I can still re-order them in the LazyColumn no ?
z
So you're still missing items, not just seeing them out-of-order?
a
yes
okay using a list works !
z
Are you sure your IDs are unique?
a
and I have all my messages in order
z
I see you're generating IDs like this:
Copy code
val now = Clock.System.now()
		id = "${now.toEpochMilliseconds()}${now.nanosecondsOfSecond.toString().takeLast(3)}".toLong()
But i'm not sure if
Clock.System.now()
will give you nanosecond accuracy – if it doesn't, then you're probably generating duplicate IDs.
That's probably not the best way to generate IDs for other reasons (e.g. testability), might be better to use a monotonically-increasing int counter.
a
yes you're right, but I didn't find another way to generate very sure ids without invoking some non-secure randomness (I'm not very good at security and pseudo-random number generation also)
z
How to generate IDs is a whole other discussion, but i'd recommend finding another approach so they are guaranteed unique. But also using a list data structure if the order is meaningful (which it usually is in a UI).
a
yeah okay I see, thanks for the tips !
okay the error was just that I was getting duplicated IDs