Hi, what do you guys use for LiveData single(one t...
# android
k
Hi, what do you guys use for LiveData single(one time) Events, previously i was using
SingleLiveEvent
but previously i had seen some type of
Event
(don't know exactly what it was called) class. i have seen that class somewhere in google architecture samples or in I/O talk but now i'm unable to find that, can you guys please let me know if
SingleLiveEvent
is best for one time event or is android team provided some other solutions ?
d
Not strictly kotlin related. I've read multiple times that one should use regular
LiveData
with a flag for single events.
r
@Dominaezzz I never read that but I like that idea, than making all these liveevent custom data holders.
a
tbh I think events in LiveData is an antipattern. The only reason it's come up in our docs and blog posts is because we haven't wanted to confuse people by asking them to learn Rx or Flow.
๐Ÿ‘ 6
LiveData is for state and LiveData observers should be idempotent; the whole thing about marking event holders inside LiveData as consumed is a workaround for when you don't have a more appropriate option.
r
@Adam Powell LiveData is a data holder. Rx and flow are for streams of data
a
And a data holder is not appropriate for events. ๐Ÿ™‚
r
I wouldn't call it so much a anti pattern if your always passing a value. SingleLiveEvent and LiveEvent just pass in a null value under the hood. But if you create like a sealed class of ActionFlags I can see that very useful
The pattern your trying to maintain is that view is only observing the viewmodel. And that will maintain that
a
It's an antipattern to attempt to change the behavior of later observers by mutating a container passed through a LiveData in an earlier observer before other observers see it.
r
not sure what you mean
give me an example
a
the common form of the SingleLiveEvent pattern is to pass some sort of wrapper (Event class) or change the LiveData behavior in a subclass
r
right
a
so if you have two observers watching a LiveData, the first will take some action to consume the data, and the second will no-op
that's super janky and is a workaround for wanting to straddle a world between state with idempotent observers and a world of event streams when all you have is the LiveData hammer
if you want to use LiveData, use state with idempotent observers so that it doesn't matter that all observers do in fact see the new value of the LiveData
the common example that I see brought up is some sort of command to navigate somewhere
you could see that as some sort of SingleLiveEvent that expresses, "please navigate to X", which is not (necessarily) an idempotent operation. If it happens more than once you might end up with a mangled back stack, or some other side effects.
whereas the state-driven, idempotent observer way of looking at that is for the LiveData to contain a value that says, "the user should be looking at X" - it's just a value, not a pending command.
r
interesting word choice there ๐Ÿ˜‚ But its not really a big deal, each observer would just have logic specifying the type of LiveExpecting. In the traditional LiveEvent subclass, there would be a isHandled property passed to the observer
d
I agree with Adam on this one. The SingleLiveEvent class is just hackery.
a
that's one of the problems with the initial form of SingleLiveEvent as a different LiveData subclass - it violates LiveData's contract and is no longer substitutable as LiveData
LiveData goes through great pains to make sure that
.getValue()
agrees with the value seen by observers
d
The are more elegant solutions to solving these problems.
a
if
.getValue()
would return something different, active observers are told about it.
SingleLiveEvent deliberately violates this
and makes the subclass no longer a proper subclass
d
Flow is a perfect solution for this kinda issue.
a
yep
r
I think the better solution is what @Dominaezzz said earlier, to use Flags instead
and just use liveData
a
flags on what, a data holder passed as the LiveData's value? You're just digging the hole deeper at that point
d
There is no rule written on a stone somewhere that states your viewControllers can not subscribe to multiple streams of input.
r
Of course not . its a simple concept ..
a
Let LiveData be the state holder it wants to be; either re-envision what you're passing through it as state so that you don't impose this kind of complexity on your downstream consumers or use a stream type better suited to these patterns
LiveData is already a lot of complexity on top of reactive stream type ideas in order to present the state holder abstraction in the way it does
(we've had a lot of debates on this around the office throughout LiveData's development and the years since ๐Ÿ˜„ )
r
Untitled
this is pseudo so don't judge me hahha
but I think that solution is ok
a
goodness, I wish slack would let you break a thread out into the main pane more effectively, reading this in a 4cm column is unfortunate ๐Ÿ™‚
(and yes, I know that code snippets you can break out)
r
So lets see . For people saying Flow is a simpler solution for this . lets see your code snippet and compare
a
for parity here let's say you did the data hiding thing with
Copy code
private val _events = MutableLiveData<Action>()
val events: LiveData<Action> get() = _events
to prevent outside things from writing into it, since you've provided the ViewModel functions to write already
take that same pattern:
Copy code
private val _events = BroadcastChannel<Action>()
val events = _events.asFlow()
r
write the whole transition
this is just declaring two properties
a
the only other part you have there is the fragment, replace your
observe
call (to which you forgot to specify a lifecycle to observe in ๐Ÿ™‚ ) and do
Copy code
appropriateScope.launch {
  viewModel.events.collect {
    when (it) { ...
and replace your writes with
.offer
- you can decide what your backpressure behavior should be via the
BroadcastChannel
constructor - default is drop if no one is listening, first wins if you use
1
as a buffer size, last wins if you use
Channel.CONFLATED
you expose more knobs to tune for the person writing the abstraction and as a tradeoff the consumers get simpler and can use standard composable pieces - coroutine scopes, suspending calls - instead of special types you have to create a cottage industry of code around like
Event<T>
holders
but again, if you send state instead of actions, LiveData is just swell here ๐Ÿ™‚
r
ahh I think I see what your trying to do , but at this point did you even need flow ? But I think LiveData is still abit more of the simpler solution . But in a case were your handling the result of asynchronous network events than this is the better the solution
You still need to create custom objects. Because you need to know the type of action you just collected
so a flag would make sense with both i think
a
why would a flag make sense if you're not trying to work around LiveData holding the event after something has already handled it?
r
oh I see . each action listener would have its own flow
a
note that using the conflated broadcast channel is going to hold onto a copy in a manner similar to LiveData
but using an unbuffered channel will just emit to whatever's listening at the time
r
conflated broadcast channel holds a copy of the last thing emitted?
r
Hey guys gotto run, wife calling lol. But intresting topic. Im going to code both ways out as proof of concept and post it here when I return from shopping๐Ÿ˜”๐Ÿ˜”
๐Ÿ‘ 1
enjoy the Saturday morning
๐Ÿณ 1
w
Thatโ€™s one interesting thread. Iโ€™ll bring another point though โ€” I fully understand the idea that LiveData is just a data holder, and that streams/flow is more appropriate for actual action events. But LiveData also solves another problem, which is only passing data to observers in correct lifecycle state. So now if I had a Flow or Rx stream passing navigation events to the Activity for example, I can easily end up with an IllegalStateException due to manipulating fragments after onSaveInstanceState. Similarly, I would be dropping some events when I pass them to a Fragment thatโ€™s not visible (and with conflated channel Iโ€™d still receive the value more than once). From what I understand it should be now viewโ€™s responsibility to maintain this pending state and only act once itโ€™s ready, but thatโ€™s going back to error-prone, pre-LiveData times and itโ€™s pretty annoying. Am I missing something? Iโ€™m using LiveData with Event class but only because I think that at this point itโ€™s the most practical solution. Iโ€™d love to know if itโ€™s not
a
It's a problem that doesn't exist on either side of the event stream/observable state divide that the live-event stuff tries to straddle, it's sort of a smell that the data flow you're working with is already kind of muddled.
Activities, fragments, viewmodels, they all sit at a view layer in your application that at any point is a reflection of the actual application state that is independent of all of that. The only data that should live in the view layers there are at most transformations on that state.
if something can go wrong because an event got missed by the view layer, then the wrong things own those event-generating operations or the wrong things are responsible for acting on them while listening to them. If something can go wrong because the same state is observed twice, then state-observing operations are not idempotent and should be.
SingleLiveEvent
and
LiveData<Event<T>>
are workarounds to avoid solving those underlying problems that will continue to be a source of confusion and bugs.
k
@Adam Powell@rkeazor , whats the conclusion here ? , suppose i have to send event from ViewModel to go to next screen after successful login then what should i use ?
SingleLiveEvent
vs
LiveData<Event<T>>
vs
Flow
?
g
Depends on your architectural style, but one option is to have a Flow emitting the target screen and the view/activity navigates to it, if it is not the target screen itself. When using Fragments, only collect the Flow while resumed to avoid IllegalStateExceptions.
r
I think it depends on your taste a-little. Currently the only true way to achieve it in flow is to wrap it with a Hot stream of event. Which is fine, there are even a-lot of good strides to make that happen, in the Flow API. But It also might be a bit overkill if its just a simple click event that doesn't require asynchronous work. Than I would argue live-data is the simpler solution
And also remember ,it doesn't have to be one or the other you can use everything in conjunction with each other. LiveData now has coroutine builders
and extensions. Just use things when things make sense to use them
@Kulwinder Singh
k
@rkeazor thanks, do you have any samples for
Flow
inside android, i have very good experience in coroutines but unable to understand Flow, is there any blog or something where use cases of
Flow
inside android app are shown
and @elizarov wrote a really good blog series on it https://medium.com/@elizarov/cold-flows-hot-channels-d74769805f9
๐Ÿ‘ 2
@Kulwinder Singh
@activity and fragments are at the view layer. Viewmodels are there own layer just like presenters... @Adam Powell
k
@rkeazor thank you very much for links.
a
viewmodels sit a lot closer to the view layer I was talking about above than an application repository layer. ViewModels don't hold their own sources of truth about the application, only about the UI. They only hold intermediate, processed state from the rest of the application in service of letting the view layer present it.
Follow the data flow and the sources of truth. For the login example, who holds the state about the currently logged in user? It can't be the ViewModel since that state would be lost as part of
onClear
. The source of truth must be somewhere that outlives ViewModels.
The currently logged in user state might be either null or a, "not logged in" value, or a user state object. A ViewModel might make that state available via LiveData, and observers at the Activity or Fragment layer might choose to navigate to a new destination if an observed state change of that user state means what the user is currently looking at is no longer valid. At that point the LiveData observer is idempotent; it doesn't matter how many times an equivalent state change happens or if multiple things see the new state change.
d
In the situation where we need to show a Toast or a Snack Bar or some other one off event I like to use a life cycle aware "Presenter" that knows when to start/stop collecting from a
Flow<Event>
.
I use MVI and state reducer to generate the events based on state changes.
g
To summarize for those who will come after,
Event
(currently used in google examples) is bad,
SingleLiveData
is even worse and
Flow
is not lifecycle aware. Pick your poison.
๐Ÿ‘Ž 1
r
What makes them bad and worst. And CoroutineScope is lifecycle aware, therefore you can make flow lifecycle aware.
โœ”๏ธ 2
k
@ghedeon actually i'm also interested to know that what could go wrong with
SingleLiveData
?, because i'm using this in my most of projects.
g
@Kulwinder Singh no doubt it works on your projects but did you skip the whole thread?๐Ÿ™‚ I mean, people put some very good effort here to explain why it's unsatisfactory.
e
Can you please give me TL;DR on what โ€œ`Flow` is not lifecycle awareโ€ means and why it should be aware of the lifecycle when it is a fully cold concept?
w
Roman, iirc the discussion revolved around providing view state and events from the ViewModel to the Activity/Fragment. Since ViewModel lives a lot longer than their Activity/Fragment instance, we need to make sure some events arenโ€™t passed when when A/F is in the wrong state.
LiveData
handles this for us, while RxJava/Flow doesnโ€™t. I donโ€™t think thereโ€™s any claim that Flow lacks something, it just doesnโ€™t integrate with Android lifecycle without some extra work, same as RxJava
e
Thanks
r
Umm being "lifecycle aware " in its simplest term just means knowing when to stop lol. Yes Viewmodel is Lifecycle aware but as long as your canceling your Coroutine Job or unsubscribe you Rx subscription during Viewmodel.onClear you will have no lifecycle issue or leaks. And if you do it's probably not a deficiency is in the library it's probably a deficiency in the code itself lol
d
@elizarov If the Android View is not at least in the
STARTED
state it should not be issuing updates to itโ€™s UI. While itโ€™s not in at least the
STARTED
state and collecting from a
Flow
itโ€™s collection coroutine should suspend. I am not sure if it does that as I have not looked but I think that is the concern here.
That being said it is up to the developer to ensure itโ€™s collection pauses in this state.
So can the collection coroutine be suspended and resumed manually?
I think Hannes Dorfman has already provided a solid solution to this issue though. http://hannesdorfmann.com/android/mosby3-mvi-7
r
@ghedeon the real reason why this thread is long. Is because devs like to complain lol.Devs will complain about RxJava but turn around and use EventBus๐Ÿ˜‚๐Ÿ˜‚. Or complain about databinding without ever trying it. SingleLiveEvent is a pretty simple concept and a even simpler implementation.There.. LiveData in its entirety is just a data holder that can be observed. It's not even on the same scale as the Flow api or Rx Java. So I'm not even sure why people debate about it so much .. They are all good api's. You just choose what fits your usecase
โœ”๏ธ 1
d
Right tool for the right job give you the right solution. Thatโ€™s just the way life is.