Roudy Korkis Kanaan
07/25/2022, 4:42 AMChannel
doesn't guarantee delivery in some cases. But is it a bad practice to separate the event class from the ui state class? I've attached an example. Mainly because I don't think a ui event should trigger the re-composition of ui state rendered by compose, the OneShotEventEffect
would just re-compose itself. Or are we supposed to have the entire state of the ui in a single class?Alex Vanyo
07/25/2022, 6:16 PMMutableStateFlow<MyUiEvent>
, it reads like you are prolonging the handling of an event and keeping it in-flight for longer to be handled later. When you call triggerEvent
, that sort of reads as “tell the UI to do this thing” (both of which are antipatterns as described in the article)
Telling the UI to do something is not directly possible. If the UI isn’t available, then multiple calls to triggerEvent
will clear out the previous “event” that was sent, without it being handled. Maybe that’s what you want, and maybe not, depending on the exact situation. Another weird issue that might happen because block
is suspending: What happens if, while block
is in the middle of running, triggerEvent
is called again?
I would try to turn triggerEvent
into updating some state that more accurately reflects why you want to go to a different screen. Then, the LaunchedEffect
reads more like “I am currently showing this Composable
, and the state I am getting from the ViewModel
says Y. Therefore, I should navigate somewhere else, and maybe tell the ViewModel
I did so”Roudy Korkis Kanaan
07/26/2022, 5:44 AMChannel
or a having it all in the one state. But in saying that, don't you think it's also an anti pattern that the ui is telling the viewmodel that a state has been handled? If the ViewModel has a requirement of an "Event" to be consumed only once, but the ui never tells the ViewModel that it consumed it then you can keep consuming that even over and over again.Roudy Korkis Kanaan
07/26/2022, 5:44 AMViewModel
function that lets it know that that event has been handled.Roudy Korkis Kanaan
07/26/2022, 5:45 AMViewModel
side where it can only be consumed onceAlex Vanyo
07/26/2022, 5:41 PMfind a way to make that event a true single shot event from theThat problem is really, really hard in general. An analogy that Adam Powell offered a while back that I really like is imagining aside where it can only be consumed onceViewModel
ViewModel
as a server, and your UI as a client. It’s not as simple as just having the server tell the client to do something and guarantee it will happen. What if the network call fails? What if the client loses internet and never gets the message? What if there are multiple clients?
don’t you think it’s also an anti pattern that the ui is telling the viewmodel that a state has been handledIt’s not ideal, in the sense that
LaunchedEffect
s should be avoided in favor of hoisting state. Using this pattern for everything in your app would not scale well. But for something like navigation, which requires a call to originate from the UI (since a navController
is UI-scoped) is when a LaunchedEffect
makes sense. The UI is telling the viewmodel to update state after seeing previous state. And that matches up with the data flow in UDF.
A different non-navigation example: Say we want to animate scrolling a list to specific place. We can’t directly do that from a `ViewModel`: The UI might not be visible, we don’t know if other animations are happening. So we could expose a itemToScrollTo
, that the UI can act upon when it is ready, and then tell the ViewModel
we’ve finished the scroll.
The UI (if and when it exists) is a temporary “actor” in the presence of state, in the sense that it can do things as a response to user interaction or automatically.
If the ViewModel
state includes an error to show, then perhaps the UI shows a dialog waiting for the user to cancel or retry. Upon making that selection, the UI tells the ViewModel
state that the user has given a response to that particular error (perhaps it has a unique id)
Maybe instead the UI shows the error in a transient way, with something like a snackbar. Now, the UI will wait for some period of time (to give the user a chance to see it), and then update the ViewModel
saying that particular error has been shown.
One step further, the ViewModel
is exposing some state, and the UI knows it can immediately react to it and do something, and then update the ViewModel
to tell that it did that.
It also means in unit tests, following any function that would trigger an event (That would update the state), it must be followed by someRight, and I would view that as an advantage: it forces awareness that afunction that lets it know that that event has been handled.ViewModel
ViewModel
can exist without the UI available. You could write a test where multiple ViewModel
calls happen in a row, and there still hasn’t been the call that would normally come from the UI. And to reiterate: the ViewModel
should not be storing an “event” in-flight, that “tells” the UI to do something. It should be storing state, that can be acted upon by the UI. Admittedly, that doesn’t look that technically different, it’s more about naming and perspectiveChristopher Elías
07/26/2022, 7:43 PMRoudy Korkis Kanaan
07/26/2022, 11:48 PMViewModel
, I guess it just requires some small mindset change then it won't seem weird anymore. Another thing I noticed in the Now In Android doesn't really use the state to reflect navigation but instead just does all navigation straight in the Composables (as lambdas passed to the screen and calling them onClick). Do you know of any good resource with some more examples on this? Just curious about good naming conventions/patterns. Thanks again for the answer Alex!myanmarking
08/05/2022, 8:22 AM