https://kotlinlang.org logo
Title
a

Ayfri

09/02/2021, 5:11 PM
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
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

hfhbd

09/02/2021, 5:28 PM
MutableMap... Never use mutable classes with Compose. Use
mutableStateOf()
or
mutableStateMapOf()
a

Ayfri

09/02/2021, 5:40 PM
How ?
z

Zach Klippenstein (he/him) [MOD]

09/02/2021, 5:55 PM
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

Ayfri

09/02/2021, 5:56 PM
channel is an instance of a class
Channel
not related to coroutines
I'm generating the messages in main()
z

Zach Klippenstein (he/him) [MOD]

09/02/2021, 6:00 PM
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

Ayfri

09/02/2021, 6:00 PM
Oh okay I see
I'll try thanks !
a

Ayfri

09/03/2021, 5:30 AM
I still have the problem using
mutableSteMapOf
:(
But now the messages are not even in order
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 5:08 PM
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?
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

Ayfri

09/03/2021, 6:05 PM
okay thanks I'll fix these and retry
yes it's the only place @Zach Klippenstein (he/him) [MOD]
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:10 PM
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

Ayfri

09/03/2021, 7:12 PM
I still have the problem
very weird numbers
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:14 PM
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

Ayfri

09/03/2021, 7:14 PM
oh yes you're right
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:17 PM
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

Ayfri

09/03/2021, 7:20 PM
the order is not very important I guess, I can still re-order them in the LazyColumn no ?
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:21 PM
So you're still missing items, not just seeing them out-of-order?
a

Ayfri

09/03/2021, 7:22 PM
yes
okay using a list works !
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:22 PM
Are you sure your IDs are unique?
a

Ayfri

09/03/2021, 7:22 PM
and I have all my messages in order
z

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:24 PM
I see you're generating IDs like this:
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

Ayfri

09/03/2021, 7:25 PM
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

Zach Klippenstein (he/him) [MOD]

09/03/2021, 7:27 PM
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

Ayfri

09/03/2021, 7:28 PM
yeah okay I see, thanks for the tips !
okay the error was just that I was getting duplicated IDs