https://kotlinlang.org logo
#compose
Title
# compose
t

tad

07/16/2021, 12:37 AM
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

Adam Powell

07/16/2021, 1:04 AM
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

tad

07/16/2021, 3:08 AM
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

dimsuz

07/16/2021, 9:48 AM
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

ppvi

07/16/2021, 12:52 PM
we're working on official guidance
K 5
3
c

Colton Idle

07/16/2021, 1:10 PM
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

ppvi

07/16/2021, 1:25 PM
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

Adam Powell

07/16/2021, 1:29 PM
Yeah I'm curious to see some example code if you have some to share
c

Colton Idle

07/16/2021, 1:38 PM
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

ppvi

07/16/2021, 1:39 PM
to be clear: the single event thing is unrelated to the mutable data holders question
2
a

Adam Powell

07/16/2021, 2:57 PM
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

Colton Idle

07/16/2021, 3:25 PM
@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

Adam Powell

07/16/2021, 3:46 PM
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

ppvi

07/16/2021, 4:03 PM
agreed
d

dimsuz

07/16/2021, 4:14 PM
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

Adam Powell

07/16/2021, 5:44 PM
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

Colton Idle

07/16/2021, 5:46 PM
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

Adam Powell

07/16/2021, 7:10 PM
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

Colton Idle

07/16/2021, 7:15 PM
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

Adam Powell

07/16/2021, 7:15 PM
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

Colton Idle

07/16/2021, 7:18 PM
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

tad

07/16/2021, 7:19 PM
unfortunately the state is not just "current screen" but also the back stack
a

Adam Powell

07/16/2021, 7:20 PM
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

Colton Idle

07/16/2021, 7:24 PM
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

Adam Powell

07/16/2021, 7:29 PM
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

Colton Idle

07/16/2021, 7:31 PM
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

Adam Powell

07/16/2021, 7:32 PM
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

Colton Idle

07/16/2021, 7:34 PM
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

Adam Powell

07/16/2021, 7:53 PM
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

Colton Idle

07/16/2021, 8:00 PM
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

Adam Powell

07/16/2021, 8:02 PM
now I have business logic in my fragment then
don't you have this where you wire up click handlers already?
c

Colton Idle

07/16/2021, 8:06 PM
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