https://kotlinlang.org logo
Title
f

fuad

03/06/2023, 6:21 PM
I have two Flows exposed in my ViewModel,
val messages: LiveData<List<Message>> by lazy {
    repository.getAllMessages().asLiveData()
}

val users: LiveData<List<User>> by lazy {
    repository.getAllUsers().asLiveData()
}
I want to show in my UI a list of messages, and I want to render the usernames for each message. The
Message
data class has a property
userId
which should match a
userId
in
User
. I created another data class
/**
 * a [Message] with additional user information
 */
data class MessageUiModel (
    val message: Message,
    val user: User,
    val id: String = message._id
        ) {
    constructor(message: Message, user: User) : this(
        id = message._id,
        message = message,
        user = user
    )
}
How do I iterate or combine the two flows to create this third flow for display in my UI? I’ve been hitting my head on the wall with
.map
,
.combine
,
.merge
etc. Thanks so much in advance.
g

Giorgi

03/06/2023, 6:46 PM
what went wrong with
.combine
? For example, I cam up with this code
val twoFlowsInOne by lazy {
    repository.getAllMessages().combine(repository.getAllUsers(), ::Pair).asLiveData()
}
will it work?
I know it will return Pair, but you could later pass a mapper for MessageUiModel instead of
::Pair
k

Kevin Worth

03/06/2023, 8:09 PM
You may also want to look into
flowA.flatMapLatest(flowB)
which will cancel/restart when either of the 2 changes (and if just one of them changes - say B - then it will use the latest value of A, and vice versa - as far as I understand)
f

fuad

03/07/2023, 4:40 AM
The issue I'm having I think is I can't seem to figure out how to map / parse the streams properly. What I want is to find the
User
that has a matching `userID`` for each
Message
, and create a new stream of the combined objects. I think what's happening is I'm getting back a single object instead of a List. This is my attempt to combine the two flows in the ViewModel:
val messagesWithUsersFlow: Flow<List<MessageUiModel>> = combine(
        repository.getAllUsers(),
        repository.getAllMessages()
    ) { users: List<User>, messages:List<Message> ->
    messages.map {
        return@combine MessageUiModel.invoke(
            message = it,
            users = users
        )
    }
} as Flow<List<MessageUiModel>>
where, I have now updated the data class with an
.invoke
to try to handle the parsing:
data class MessageUiModel (
    val message: Message,
    val user: User,
    val id: String = message._id
        ) {
    companion object {
        operator fun invoke(message: Message, users: List<User>) : MessageUiModel {
            var messageSender: User? = null
            for (user in users) {
                if (user.id == message.userId) {
                    messageSender = user
                }
            }
            messageSender?.let {
                return MessageUiModel(user = messageSender, message = message)
            }
            val noUserFound = User()
            return MessageUiModel(user = noUserFound, message = message)
        }
    }
    constructor(message: Message, user: User) : this(
        id = message._id,
        message = message,
        user = user
    )
}
And then in my Composable UI I try to consume it like this:
val messagesWithUsers : List<MessageUiModel> by viewModel
    .messagesWithUsersFlow
    .collectAsState(initial = emptyList())
And when I try to run the app, it crashes with this exception:
java.lang.ClassCastException: c..ast to java.util.List
k

Kevin Worth

03/07/2023, 2:42 PM
What line of code is it crashing on?
f

fuad

03/07/2023, 4:29 PM
Issue solved! I removed
return@combine
and everything works great after that. The return shouldn’t have been in the mapping function
Thanks everyone for the help and suggestions