Matthew Laser
02/10/2022, 5:44 PMViewModel
, along with a data class MyUIState
. I’m initializing my property as such: var uiState by mutableStateOf(MyUiState.empty)
. , where empty
is just a default initializer with default values.
I’m then updating individual properties of uiState
as events happen, for instance uiState.loading = true
The `ViewModel`s is passed into my composable MyScreen
as such from MyActivity
setContent { MyScreen(viewModel) }
Finally, inside of my composable MyScreen
I'm doing val uiState = viewModel.uiState
and driving changes off of those values, for instance if (uiState.loading)
- however, this is the point at which they aren't recomposed.
Most of the examples I can find use a very simple value like mutableStateOf(true)
, so I'm assuming there is something going on where just updating a property isn't having the right effect.Casey Brooks
02/10/2022, 5:50 PMmutableStateOf
container. Mutable objects, in general, are going to cause lots of problems for you in Compose.
Instead of having your state have var
properties, they should all be val (thus making the class immutable), and you copy the class to change its values, re-assigning it to uiState
. Or better yet, use StateFlow
to hold your state in the ViewModel and use .collectAsState()
to avoid coupling the ViewModel directly to ComposeMatthew Laser
02/10/2022, 6:21 PMFlow
in this basic case?Rick Regan
02/10/2022, 6:41 PMuiState
is changed. Presumably, the somethingRelatedToBusinessLogic()
function is doing what Casey said ("_copy the class to change its values, re-assigning it to `uiState`_").Casey Brooks
02/10/2022, 6:41 PMmutableStateOf()
and StateFlow
in the ViewModel will do the same thing, functionally. It's more a matter of semantics, keeping the ViewModel as something that is isolated from the UI and doesn't even know about anything Compose, so that it will still work in something like a Unit Test that isn't running Compose. StateFlow
is a more general concept and offers a few extra APIs like .update { }
that make it more suitable in a ViewModel, when it may get updated from multiple coroutines/threads concurrentlyMatthew Laser
02/10/2022, 6:44 PMuiState.loading = true
or uiState = uiState.copy(loading = true)
don't trigger a recompositionCasey Brooks
02/10/2022, 6:55 PMMatthew Laser
02/10/2022, 6:56 PMcopy
function out of this documentationRick Regan
02/10/2022, 6:59 PMuiState = uiState.copy(loading = true)
to trigger recomposition. You might have to show more of your code.Casey Brooks
02/10/2022, 7:01 PMuiState = uiState.copy(loading = true)
should trigger recomposition, because you're changing the value of the mutableStateOf
or StateFlow
itself. But uiState.loading = true
will not, because it's only mutating the state object in-place, which neither Compose nor StateFlow
is tracking the individual properties ofMatthew Laser
02/10/2022, 7:07 PMDialog
like so:
@Composable
fun MyScreen(viewModel: ViewModel) {
val uiState = viewModel.uiState
MyAppTheme {
if (uiState.loading) {
LoadingDialog()
}
// the rest of my screen
...
}
}
uiState
is getting updated via the copy
call by setting the loading
value to the value of a Text
and watched it change the string printed to screenif
check, as in:
// if (uiState.loading)
LoadingDialog()
// }
the LoadingDialog
displays as desiredif
isn't somehow being marked as dirty or something that drives recompositionRick Regan
02/10/2022, 8:40 PMuiState
is getting updated with new state that has loading = true
but it's hard to reconcile that with LoadingDialog()
not running. If you could post a minimal example with the exact way you update maybe we could figure it out ...Matthew Laser
02/10/2022, 8:41 PM