I think <@UJBKXD0HH> might have something to say a...
# compose
t
I think @Adam Powell might have something to say about this article: https://medium.com/google-developer-experts/jetpack-compose-missing-piece-to-the-mvi-puzzle-44c0e60b571
(mainly the promotion of
Flow.collectAsState
for Compose UI state)
a
What about it?
βž• 1
it's a direct translation of an existing pattern to use compose; I think the only thing I'd change about it would be to use a constant for the
initial =
value for
collectAsState
vs. constructing a new one to pass that gets discarded πŸ™‚
A few things about it I think nudge toward some additional/alternative tools as later steps, but ultimately they're optional. The nested
.copy
calls will start standing out next to other code that was simplified in the migration, and tools like arrow lenses or snapshot mutable state start to look compelling for addressing some of that
ultimately compose is meant to be a toolkit rather than a framework; so long as you can BYO data, compose will present it for you without forcing major changes in your data modeling. Posts like this are success stories in that πŸ™‚
☝🏼 1
☝️ 3
☝🏻 1
some of my more recent posts in this channel around
collectAsState
usage that nudge away from it are more around things that muddle the distinction between state and events, or that jump through a lot of hoops for creating different forms of derived state using `StateFlow`s
the mobius-style conventions on display in the linked post generally don't run into those issues
t
Legit, thanks for this. I do still think snapshot state is the best option if your project is pure Compose, but I'll be a bit more thoughtful about alternative data patterns going forward.
d
I'd love to read something more on state vs events, separating what is state and what is event, I mean something about the way to think about this. I know now I have this separation in my head, after half a year ago I happened to have a conversation with Adam here and he suggested this separation of concepts: this really had shifted some gears into place for me :)
πŸ‘ 1
p
we're working on official guidance
K 5
βž• 3
c
Awesome. Thanks Jose. That should be extremely useful. Especially curious about a hopefully simple way to send single events between VM and your composable/fragment. Using compose and representing state using snapshot state and not having to do the "backing property" pattern with LiveData is a breath of fresh air. Hope some similar simple pattern becomes popularized for events.
p
that's interesting. Backing properties are only needed in
MutableLiveData
or
MutableStateFlow
but most exposed state comes from another Flow anyway so they're the result of a
combine
,
flatMapLatest
, etc. Are backing properties that common and annoying in your codebase that you want a completely different pattern? Could you be abusing the mutable data holders?
a
Yeah I'm curious to see some example code if you have some to share
c
Could you be abusing the mutable data holders?
Most likely. Adam Powell once I'm back at my machine I will try to send something across. I think it's mostly due to different libraries that we've used in order to do this. We used to use the SingleLiveEvent from that article from Jose that was written a while back, and ever since we've been bouncing around different libraries.
p
to be clear: the single event thing is unrelated to the mutable data holders question
βž• 2
a
The best summary I know of so far is if your producer outlives your consumer you should produce state, not events. If your consumer outlives your producer you should usually produce events, and let the consumer own and aggregate state.
πŸ‘ 1
c
@Adam Powell ooh. That's a fairly nice way to put it.
βž• 2
@ppvi oh boy. a lot of that went over my head. I just don't have a phd in coroutines, flows, channels etc so that stuff is scary. I guess I should just learn it one day. But overall it's just a lot to grok for just trying to send a signal from VM to my activity that a navigational event was triggered.
a
what the producer/consumer lifetime guideline says is that it's conceptually fraught to send events from a longer-lived producer to a shorter-lived consumer and working with state instead in those situations avoids a lot of downstream design considerations and edge cases to handle. It's a generalization of a lot of the underpinnings of compose's state hoisting patterns.
the approach in your reddit comment above, @ppvi, is a conceptually sound way of solving the first step of a harder problem than I think is necessary to solve in these use cases
p
agreed
d
I wonder what are examples of events in Compose? It seems like there's state everywhere πŸ™‚ Is "onClick" the event you are talking about?
a
it's an example of one, yes. The caller of a
Button
composable is the consumer by way of the
onClick
function passed as a parameter, and it outlives the
Button
itself (the event producer)
c
I guess I'm not seeing that as an event. For all events like onClick and onTextChange, I just have those as methods in my VM. For me the events are the ones that my VM has to emit to my activity so that it can perform some action, like navigation, or fire off an intent or something.
a
a method can be an event; that's exactly what things like
View.OnClickListener#onClick
are πŸ™‚
as your VM (assuming arch components VM) outlives your Activity, by the guideline above events from VM to Activity shouldn't exist at all, VM should only publish state to the Activity instead. The activity can observe the VM state and other states of its own and decide to navigate, startActivity, etc. in response - the event comes from a producer that doesn't outlive the activity - an observer of the VM state that doesn't outlive the VM either.
c
Interesting... So if I have single activity, multi fragments and each fragment is just a composable screen. I have one VM per fragment. In my fragments VM that's where a button click event would in turn emit an event to go to the next screen. So @Adam Powell you're saying that I should instead represent that as state?
a
that depends on what it actually represents
based on the description there, there's a reasonable argument that the VM shouldn't be the authority in choosing to navigate to a next screen at all
c
Dang. And here I was thinking I had this whole thing pretty much figured out. I always considered that the "current screen" should just be a state that global to the app. Maybe something like that represented as state on the activity level would be more appropriate?
t
unfortunately the state is not just "current screen" but also the back stack
a
the idea still works if you include the back stack in the idea of current navigational state
different navigation models in android complicate things since you need an Activity to startActivity while correctly keeping your task intact, jetpack navigation needs the navigation controller which has similar scoping requirements
but the jetpack navigation state (and your activity task state, for that matter) outlives the activity too
the guideline above is still preserved: the activity/fragments/NavHost observe navigation state that outlives the activity, and you send events to it by way of navigateTo calls
c
Hm. Yeah I'm using Compose navigation. I guess I just have to figure out the "right" was to go from fragmentA to fragmentB. I'll read the docs again. Maybe I missed something.
a
it's just a generalization of state down/events up, but with definitions of "down" and "up" that speak to the associated scoping lifetimes rather than something strictly related to a UI tree
c
Yeah. To me it's just like. I'm using all jetpack AAC. Fragments. Nav. vM. Now I have fragment a and b. A has a button. That event of the button being clicked goes up to the VM. Now what. How do I go from a to b?
a
well, that's kinda the point. You sent a signal up to space instead of across the room on your local wifi network. πŸ˜› Skip the detour/layover in the VM.
c
But what if the VM does some sort of validation on button press. I.e. is the form filled out? Yes? Send user to fragment b. If not, update the state with showErrorDialog = true or something.
That should still surely go through the fragments VM?
If not, then I'm missing something and idk how to android anymore and I officially resign. Lmao
πŸ˜‚ 1
a
if (vm.validate(info)) { navigator.navigateTo(next) }
is still valid code but doesn't have to live in the vm itself, it can live in a place where both
vm
and
navigator
are in scope
c
Interesting. I feel like now I have business logic in my fragment then. But anyway. This definitely gives me something to think about. I'm going to read about plain compose with AAC nav because I'm curious how that'd work. I'm interested in ditching fragments and AAC VM entirely, but from what Ian said, it seems like it'd still be easier to do pure compose and keep AAC VM.
a
now I have business logic in my fragment then
don't you have this where you wire up click handlers already?
c
I guess. I mean there has to be something concrete between the two. But yeah. Idk. Maybe I'm overthinking it. Maybe my brain is fried on this scorching Friday. Appreciate the help. May bounce a couple of things off of ya later. Really appreciate it though.
πŸ‘ 1