https://kotlinlang.org logo
#flow
Title
# flow
a

Andrea

02/23/2022, 11:43 PM
Hi everyone, I have a doubt regarding the flatMapLatest and the UDF. I have pasted a simplified code version with more details. I would like to create a UIState in my viewmodel, with several properties (example like HomeUiState in JetNews). One of these properties is a list of users that might be filtered by the user. 1 - The first approach would be exposing the list, which would be filtered every time the userId changes. In this case I would not be able to add the list into my UIState. 2 - The second one would be collecting the flow into the init and update the UIState in the onEach. In this case though, I would need to trigger the userId from the UI in order to start it the first time. Any suggestions for situations like this? Am I doing anything wrong? Thanks!
Copy code
data class HomeUiState(
    val users: MutableList<User>
)

class HomeViewModel(
) : ViewModel(){

    private val repository: Repository = Repository()

    private val viewModelState = MutableStateFlow(HomeUiState(mutableListOf()))
    val uiState: StateFlow<HomeUiState> = viewModelState
        .stateIn(
            viewModelScope,
            SharingStarted.Eagerly,
            viewModelState.value
        )

    val userId = MutableStateFlow(1)

    //  1 solutions: Keep the list out the UIState and collect it in compose with collectAsState 
    val userList = userId.flatMapLatest {
        repository.getUsersList()
    }.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000),
        emptySet()
    )

    // 2 solution, flatMapLatest for the filtered list
    init {
        userId.flatMapLatest {
            repository.getUsersList(it)
        }.onEach { users ->
            viewModelState.update {
                it.copy(users = users) }
        }.launchIn(viewModelScope)
    }
}
n

Nick Allen

02/24/2022, 12:18 AM
1. I suggest List instead of MutableList when passing data around. 2. You don't need to call
stateIn
on a
MutableStateFlow
, it is a
StateFlow
. If you are worried about callers casting it, you could call
.asStateFlow()
. 3. Seems like you want the
HomeUiState
so just go with that 4.
stateIn
implementation starts a coroutine that collects and updates a
MutableStateFlow
so there's really no difference conceptually. Just use
stateIn
since it has the code you need already. You could do this with just one expression:
Copy code
val uiState: StateFlow<HomeUiState> = userId
    .flatMapLatest { repository.getusersList(it) }
    .map { HomeUiState(it) }
    .stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000),
        HomeUiState(emptyList())
    )
If there's more to
HomeUiState
then you can use
combine
to build it from multiple `Flow`s.
a

Andrea

02/24/2022, 2:01 AM
Thanks for the advices 1 and 2, I have removed and clear the code to paste it here and I didn’t notice them. You are correct regarding the List and the stateIn, thanks. I like the solution of the map, but what if I have another property in my HomeUiState? I would need for example,
Copy code
data class HomeUiState(
    val users: List<User>,
    val messages: List<Message>
)
and messages value should be updated in another fun inside the viewmodel using, ad example
Copy code
fun loadMessages() {
    viewModelScope.launch { 
        // call repository and load someeMessages
        viewModelState.update {
            it.copy(messages = someeMessages)
        }
    }
}
I need to keep the private mutableStateFlow to update other properties. I have tried this solution, but maybe there is a better one
Copy code
val uiState: StateFlow<HomeUiState> = userId.flatMapLatest {
        repository.getUsersList(it)
    }
    .map {
        HomeUiState(it, listOf())
    }
    .combine(viewModelState) { f1, f2 ->
        viewModelState.update { it.copy(users = f1.users) }
        viewModelState.value
    }
    .stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000),
        HomeUiState(listOf(), listOf())
    )
n

Nick Allen

02/24/2022, 2:54 AM
Copy code
combine(usersFlowexpression, messagesFlowExpression, ::HomeUiState)
   .stateIn(…)
When you combine, it gives you the latest from each flow. You can then build the HomeUiState from these latest values.
If the flows don't match your data constructor exactly, you can use a lambda instead of constructor reference.
a

Andrea

02/24/2022, 11:36 PM
Thanks for your advices, it works and I didn’t think about using the combine in this case, thank you!
2 Views