https://kotlinlang.org logo
Title
s

svenjacobs

08/18/2022, 9:51 AM
Hello, following the recommended Compose architecture for Android, I usually provide a UI state in my ViewModels in the following way:
val uiState: StateFlow<UiState> =
    combine(
        firstFlow,
        secondFlow,
        thirdFlow,
    ) { first, second, third ->
        UiState(
            first = first,
            second = second,
            third = third,
        )
    }.stateIn(...)
Now the problem is that combine only provides overloads for up to five
Flow
arguments. Starting with six or more arguments
combine
takes a
vararg Flow<T>
or
Iterable<Flow<T>>
. This means all Flows must be of the same type or - if not - the type becomes
Any
. Then we lose type safety and must cast to the right types, which is subpar. When I encounter this, I combine logical units of Flows into their own data classes (or
Pair
or
Triple
), something like
val combinedFlow = combine(
    firstFlow,
    secondFlow,
) { first, second ->
    CombinedState(
        first = first,
        second = second,
    )
}

val uiState: StateFlow<UiState> =
    combine(
        combinedFlow,
        thirdFlow,
    ) { (first, second), third ->
        UiState(
            first = first,
            second = second,
            third = third,
        )
    }.stateIn(...)
But even this solution has its limitations when there are no more logical units of Flows to build and there are still more than five distinct Flows. How do you approach this problem? What are your solutions?
z

Zoltan Demant

08/18/2022, 10:22 AM
Another approach is to instead use MutableStateFlow<UiState>; updating its value whenever the state changes 🙂
s

svenjacobs

08/18/2022, 10:36 AM
@Zoltan Demant I usually try to avoid using
MutableStateFlow
and setting attributes of
UiState
individually. Because this usually also means launching multiple coroutines, possibly in
init
of ViewModel? I think having one public
StateFlow
is safer (resource wise) and less verbose.
@Grégory Lureau Thanks. I guess I have to add the required
combine
variants to my project myself, like you suggested. I’m wondering if someone already wrote a library for that?
t

Tgo1014

08/18/2022, 10:59 AM
@svenjacobs Something like this?
s

svenjacobs

08/18/2022, 11:05 AM
@Tgo1014 Yeah, something like that. Thanks!
c

Csaba Szugyiczki

08/18/2022, 11:14 AM
We always emit values individually from our ViewModels in our apps, and collect them also individually in our Views, and it works well. I do not really see the advantage in combining all possible values in a State object. So you end up with this:
MyViewModel(repo: Repo) {
    val name: StateFlow<String> = repo.name
    val description: StateFlow<String> = repo.description
    val cout: StateFlow<Int> = repo.count
}
Instead of this:
MyViewModel(repo: Repo) {
    val state: StateFlow<MyState> = combine(repo.name, repo.description, repo.count) {name, description, count
        MyState(name, description, count)
    }

    class MyState(
        val name: String
        val description: String
        val count: Int
    )
}
s

svenjacobs

08/18/2022, 11:39 AM
@Csaba Szugyiczki But in your Compose code you then end up with:
val name = viewModel.name.collectAsState()
val description = viewModel.description.collectAsState()
...
instead of one single
val uiState = viewModel.uiState.collectAsState()
In the end it’s a matter of personal taste but Compose is able to efficiently only recompose those parts of a UI that are affected by changed UI state properties, even if they are combined in a single data class (assuming the data class itself is stable).
Using a single
UiState
is just what I saw in a lot of official Compose examples and blog posts lately.
c

Csaba Szugyiczki

08/18/2022, 11:42 AM
Yes, that is correct. And I agree it is a matter of personal taste indeed. I just wanted to point out that it might not be necessary (despite many sources doing it this way) to combine all your values into one large object
s

svenjacobs

08/18/2022, 11:43 AM
Another benefit of one exposed public Flow is consuming it safely from Android. Yes, it can also be done with multiple Flows but it means more boilerplate code.
a

AmrJyniat

08/19/2022, 5:00 AM