Hello, following the <recommended Compose architec...
# compose
s
Hello, following the recommended Compose architecture for Android, I usually provide a UI state in my ViewModels in the following way:
Copy code
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
Copy code
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
Another approach is to instead use MutableStateFlow<UiState>; updating its value whenever the state changes 🙂
s
@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
@svenjacobs Something like this?
s
@Tgo1014 Yeah, something like that. Thanks!
c
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:
Copy code
MyViewModel(repo: Repo) {
    val name: StateFlow<String> = repo.name
    val description: StateFlow<String> = repo.description
    val cout: StateFlow<Int> = repo.count
}
Instead of this:
Copy code
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
@Csaba Szugyiczki But in your Compose code you then end up with:
Copy code
val name = viewModel.name.collectAsState()
val description = viewModel.description.collectAsState()
...
instead of one single
Copy code
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
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
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