I have a very verbose root composable called HelloCompose
. This compose is backed by HelloComposeViewModel which contains lots of variables.
I am trying to cleanup my HelloCompose code so that it is more readable. What is the idiomatic way to encapsulate lots of state variables?
This is my current code:
@Composable
fun HelloCompose() {
val viewModelHelloCompose: HelloComposeViewModel = viewModel { HelloComposeViewModel() }
val foo: String by viewModel.foo.collectAsState()
val bar: Int by viewModel.bar.collectAsState()
val baz: String by viewModel.baz.collectAsState()
Foobar(foo = foo, bar = bar, baz = baz)
}
And this is how I have refactored it:
data class FoobarParams(
val foo: String,
val bar: Int,
val baz: String
)
@Composable
fun getFoobarParams(viewModel: HelloComposeViewModel): FoobarParams {
val foo: String by viewModel.foo.collectAsState()
val bar: Int by viewModel.bar.collectAsState()
val baz: String by viewModel.baz.collectAsState()
val result = FoobarParams(foo = foo, bar = bar, baz = baz)
return result
}
@Composable
fun HelloCompose() {
val viewModelHelloCompose: HelloComposeViewModel = viewModel { HelloComposeViewModel() }
val foobarParams = getFoobarParams(viewModelHelloCompose)
Foobar(foo = foobarParams.foo, bar = foobarParams.bar, baz = foobarParams.baz)
}
Is there a better way to refactor it?
How does one handle a situation where a viewmodel holds say, 10 variables that need to go into a composable? Do we then encapsulate these variables like this:
private val _uiState = MutableStateFlow(HelloComposeUiState())
val uiState: StateFlow<HelloComposeUiState> = _uiState.asStateFlow()
?
This is usually solved by combining the flows themselves, before they are exposed to your composables and collected.
In your view model, do something like this:
val combinedFlow: StateFlow<FoobarParams> = combine(foo, bar, baz, ::FoobarParams)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5.seconds),
initialValue = FoobarParams(...), // or null or whatever
)
Note that combine
produces a Flow
, not a StateFlow
(hence stateIn
), even when foo, bar and baz are already StateFlows. That's also the reason you need to provide an initial value here.
Then again, foo, bar and baz themselves are not required to be StateFlows anymore (and they should be private
).