Can I get a sanity check as to why my UI won’t rec...
# compose
m
Can I get a sanity check as to why my UI won’t recompose? I’ve got a simple 1 activity setup with corresponding
ViewModel
, 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
Copy code
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.
c
Properties of an object are not automatically made observable, just because it's held in an observable
mutableStateOf
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 Compose
👌 2
m
that makes total sense, but I'm a little confused as to how I'm erring in contrast to the "basic" examples provided straight from the google developer docs
and from what I could tell there was no inherent advantage to using a a
Flow
in this basic case?
r
The docs example is not showing how
uiState
is changed. Presumably, the
somethingRelatedToBusinessLogic()
function is doing what Casey said ("_copy the class to change its values, re-assigning it to `uiState`_").
c
That is missing a lot of stuff and definitely won't compile, and it looks like it's emphasizing the boilerplate to getting a handle to a ViewModel, rather than proper setup and usage of the ViewModel itself. This snippet might help you get an idea of how to handle the internals of the ViewModel, setting and updating the state. https://kotlinlang.slack.com/archives/C01D6HTPATV/p1644259697620799?thread_ts=1644257413.142249&cid=C01D6HTPATV
mutableStateOf()
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 concurrently
m
yeah @Rick Regan my main point is that I'm just following the boilerplate example - calls to
uiState.loading = true
or
uiState = uiState.copy(loading = true)
don't trigger a recomposition
I could just switch to using the Flow api, but I was just trying to see if I could solve what this disconnect was since obviously something is going sideways at a more fundamental level
and yeah @Casey Brooks thanks for the broader context - that makes sense. I realize this won't compile, but was just remarking that the "default example" of Compose state foundations uses this example, and is recommended in the Codelab that was updated as recently as yesterday
c
That snippet doesn't show setting/changing any properties of the state, though. Heck, it doesn't even include reading any properties from the state object itself... Really not a very good snippet, is it? 😂
m
hahah yeah hence the barrange of random questions on my end 😆 - very interesting approach to completely leave the
copy
function out of this documentation
r
I guess it's not clear what you're following if it's not in the code example. In any case, I'd expect 
uiState = uiState.copy(loading = true)
to trigger recomposition. You might have to show more of your code.
c
Yeah,
uiState = 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 of
m
okay - so, you guys are right, copy does the trick, and it was working before, but my code is still broken lol
I'm trying to conditionally display a
Dialog
like so:
Copy code
@Composable
fun MyScreen(viewModel: ViewModel) {
    val uiState = viewModel.uiState

    MyAppTheme {
        if (uiState.loading) {
            LoadingDialog()
        }
        
        // the rest of my screen
        ...
     }
}
I verified that
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 screen
I also verified that if I comment out the
if
check, as in:
Copy code
// if (uiState.loading)
      LoadingDialog()
// }
the
LoadingDialog
displays as desired
so it seems like the read inside the
if
isn't somehow being marked as dirty or something that drives recomposition
if that makes any sense
r
You say that
uiState
 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 ...
🙏 1
m
i will work on it - either way thanks to you both for all your help, it is much appreciated