I'm about to get started on moving several large p...
# compose
e
I'm about to get started on moving several large projects to compose, and I want to make sure that the architecture I planned on using would work well performance-wise. <tl;dr> I'm using a
Flow<SomeState>
with
collectAsState
instead of multiple
MutableState
and will rely on function skipping if the discrete fields in
SomeState
haven't changed (
SomeState
will be immutable). <\tl;dr> (longer description in thread)
I'm going for a variation on a flux architecture, where all of my UI state for a given screen will be stitched together (from 1+ sources) outside of the composable. The reason for this is that I've always had success with treating my architecture view as "dumb" and I'd like to keep it that way. It also helps enforce SRP and keeps things clean (business logic in a composable becomes a code smell that more easily sticks out in code reviews). UI events are passed to an outside layer, and they are one of the sources that the UI state is composed of. The plan for any given screen is to
collectAsState
from the UI state flow, and deconstruct it, passing the discrete state to multiple nested composables, to take advantage of the skipping aspect of compose (because it's unlikely that all state fields will mutate at the same time). My main concern is that I could run into performance issues, since this isn't really using the snapshot system. Is this a bad idea, and if not, what are some potential pitfalls to watch out for?
z
I would think that’s fine - we’re doing the same. Just make sure your
SomeState
is deeply immutable, which I assume you were already doing. If your collectAsState is relatively high in your composable tree, then if something in the leaf of your state tree changes you’ll be recomposing a little more than if everything were mutable states, but compose should still mostly only recompose the path down the tree that leads to what actually changed, and I’m guessing most of those composables would be simpler layout definitions and stuff that won’t be that expensive.
I said that’s what we’re doing - we have done it with classic views and it’s been mostly fine. Compose should be even better. But we don’t have concrete benchmarking data so I can’t say for absolute sure. But Compose is much better at skipping than Android views (and requiring feature devs to implement their own skipping), so I can’t imagine compose would be worse.
e
Good to hear, thanks! The plan is to be at or near the highest point in the tree and to have deeply immutable state. We were waiting for something like https://github.com/lyft/domic to do this with the View system, but I'm guessing that never came to be because of Compose.
Just re-read your second message. To clarify, you've been doing this with View, but haven't done it yet with Compose?
z
Correct. We’ve done some preliminary tests with compose, but aren’t using compose in production in anger yet so we don’t have any real-world data
Also I think this article discusses this stuff a bit
e
I'm actually asking my question because of that article 😅 Someone posted it on Reddit, and there was a comment saying that @Adam Powell didn't like
collectAsState
but I can't find it now (maybe after clarification the comment was deleted?)
a
it was specifically about the
initial = MovieDetailState()
in the
collectAsState
call - better to use a constant than construct a new default/empty one every recomposition just to pass it as an initial value that will be ignored after the first time 🙂
e
Ah interesting. I spent a nice amount of time in my architecture to address that issue 😂
n
This is what we do for the most part (fitness app launching soon). We combine the data into a state object using a flow combine (our repositories return flows). The bit that gets weird is when we need to maintain some local state / operate on local state / access state from the flow inside of our view model. We mostly patched this together using snapshot flows / onEach when we needed to get state into the view model but I’m still not 100% happy. Always open to others thoughts on this one 🙂
e
I had some similar issues with accessing the state outside of the composable and dealt with that by maintaining a "copy" of the Flow in a StateFlow (this library is under heavy internal development so a lot of the extra stuff in this file is gone now - https://github.com/eygraber/cure/blob/a74f72e988dd3f60bd506940dd9b961b438cdd26/core/src/commonMain/kotlin/com/eygraber/cure/RenderNode.kt#L25 ). A RenderNode is the glue class in our architecture (like a very simple fragment). It probably doesn't translate well into other frameworks, but this is one way we're currently dealing with it.