:wave: Hey, I’m seeing some overlap between `Mutab...
# compose
f
👋 Hey, I’m seeing some overlap between
MutableState<T>
and
MutableStateFlow<T>
, when should I use each?
Copy code
.collectAsState()
seems like I can use either one and just use ^^ 🤔 but I’m sure each has their own intender purposes
r
MutableState<T>
only exists in compose world but
MutableStateFlow<T>
can be used in any Kotlin code. You will need to call
collectAsState
on a
MutableStateFlow
to use it in compose.
g
There is overlap, but each has own use caase, MutableState is just a compose-ready wrapper for data, it’s not a part of kotlinx.coroutines
f
Thanks! so assuming I’m already using coroutines in my project, would it be OK to just always use MutableStateFlow and
.collectAsState()
for consistency’ sake
g
yep
MutableState can be useful as internal state of composable in some cases, but approach with always propagating events from Flow passed as composable argument and onsume it with collectAsState is better
f
good to hear, thank you! I’ll stick to that approach…
I need to remember to always call
Copy code
collectAsState()
cause it’s easy to just call
.value
.. code will compile but you won’t get updates when the underlying data changes
g
right
you anyway should never use .value in any reactive code
f
what do you mean? you ultimately always call
.value
Copy code
@Composable
fun MyText(foo : MutableStateFlow<String>) {
    Text(text = foo.collectAsState().value)
}
Copy code
@Composable
fun MyText(foo : MutableState<String>) {
    Text(text = foo.value)
}
regardless of which one you use
g
well, for MutableState yes, but you shouldn’t do this for for StateFlow
a
I would say if you don't have need to support other UI frameworks, using compose
MutableState
is a better choice. More info here.
👍 1
g
I would never use MutableState outside of compose code and rather keep my view model completely agnostic and properly testable rather than switchingto MutabelState
things consumed by your UI
I think this is my biggest issue, I would like to keep my VM agnostic to UI and more flexible
f
Thanks for the link, looks like i’m not the only one stick with this dilema
Agree with Andrey though, I’d like to keep my VMs as “android-free” as possible
but idk, I’m still conflicted between the approaches, both work OK, I might be overthinking this
g
even outsie of android, MutableState exteremely inflexible, you don’t want to expose mutability in your VM, you often want to reuse VM in another VM (like combine it with other data stream),nothing will will work with MutableState
f
you don’t want to expose mutability in your VM
good point
a
@gildor Have you heard of
derivedStateOf
?
g
Yes, I did, it’s not even closely as powerful as Flow in terms of stream flow management
Alo I would say that I more for Flow, not StateFlow (and especially MutabelStateFlow), I would try to avoid exposing state if I could
a
Of course you can use flow if you need to but avoiding compose state at all sounds like mysophobia to me.
Also by saying using
MutableState
I don't mean exposing it. This is absolutely possible (and preferable):
Copy code
class MyViewModel {
    var someVar by mutableStateOf(0)
        private set
}
g
Isn’t this will not recompose?
a
Just try it yourself.
Also this post may help.
👍 1
g
Honestly, I don’t see any advantage of this approach to convert my reactive, reusable view model into compose only state
Thanks, I read Zach’s articles
a
So you are saying that compose states are not reactive and reusable right?
And I didn't ask you convert your existing code. I'm talking about new code.
g
Especially for new code
Maybe you could you show you will test it in unit tests?
f
Thanks for sharing that article
a
Is there any difference between compose state and
StateFlow
in terms of unit test? Can you show an example?
f
Android has traditionally been hard to unit test, hence frameworks like roboelectric, not sure this applies to compose
a
Compose
State
is not Android-specific.
👍 1
f
right, good to know
Well, I’m still confused on what to use… I just wish google would stop coming up with overlapping things like this
Happened with
rxjava
and
livedata
… IMO It introduces more confusion than anything else
a
They all come with a reason. As for compose
State
, it is needed for the snapshot system, which is a core component of compose. Read the post I linked above (and other posts in that series) for more details. The third post should have answered this specific question.
f
They all come with a reason.
IDK about compose, but for livedata the reason was “it’s easier for beginners to use RxJava”. Which is a pretty bad one IMO, since now beginners need to learn even more stuff (which more experienced developers don’t really mind)
I’ll read the blockpost 🙂 hope I can clear my doubts on which one I should be using for my UseCase
a
No.
LiveData
exists because it has better interoperability with Android lifecycles.
🚫 1
f
…which can be done in Rx without stacking new concepts / classes / interoperability methods
1
c
Adam has definitely convinced me to just use MutableState unless I'm consuming something downstream that is a flow or any other observable.
a
There are many reasons to use
[Mutable]StateFlow
over snapshot
[Mutable]State
but, "it's compose-only" isn't one of them any more than, "it's kotlinx.coroutines-only" should be a reason not to use Flows. Snapshots are lower-level than the compose compiler plugin or any other compose-runtime machinery and can be used without any of it.
Declining to use snapshots as a tool on the grounds that it's shipped in the
compose-runtime
artifact and
compose-runtime
includes other things is like declining to use
suspendCancellableCoroutine
because the same artifact includes
Flow
.
Now that said... 😄
Snapshots are about state and state alone, it's a MVCC system that happens to be observable at the whole-snapshot level, and when a snapshot is committed you can see which individual elements of the snapshot changed. This is the basis for the
snapshotFlow {}
API - you can get a cold flow from any block of code or expression that reads snapshot state and it will bridge the two, no
@Composable
or compose compiler plugin required.
A great many usages of
[Mutable]StateFlow
out there would be better served by snapshots.
That said, snapshots aren't a substitute for things like
.stateIn(SharingStarted.WhileSubscribed)
they also aren't a substitute for cases where timeliness of collectors being notified is important as opposed to an, "update soon, but eventually" model. For example, Compose's own
Recomposer
exposes current state information using
StateFlow
, not snapshot state. It's a better fit for a bunch of timing and isolation-related reasons.
As for testability, personally I find snapshots to be as easy or easier to test than flows, including in isolation and in host-side tests.
You're in control over when the global snapshot transaction is committed (
Snapshot.sendApplyNotifications()
), you can perform your own isolated transactions of several changes all at once that you can then examine and assert on atomically, regardless of any other concurrency setup that might be active (the other APIs on `Snapshot`'s companion object itself)
when you're working with viewmodel-like objects (I hedge the description to make a distinction of the concept, as opposed to subclasses of android's
ViewModel
class) and consuming its state from compose using
Flow.collectAsState
, you're not using Flow instead of snapshot state, you're using Flow and snapshot state -
collectAsState
collects the flow into a snapshot state object for compose to react to. When you do this you lose the atomic transaction properties of snapshots.
4
With snapshots if you change several snapshot state objects in a transaction, observers will see those changes together, atomically. When you collect several different flows into snapshot state objects, there is no transaction tying emits from those different flows into the same atomic change. They're all separate.
If you have a ViewModel-like object with a handful of MutableStateFlows tracking state that get observed by UI code, it would almost certainly be a better fit for snapshot state instead.
f
Thanks for the thorough reply Adam! If I’m being honest most of it went over my head. I still have a lot of learning to do around compose. But I’ve saved this to re-read it when I have a better grasp of the framework
1
👍 1