I was trying out different kinds of flows like flows with channel, sharedflows and stateflows. What I did was, suppose I have a MainActivity, inside it I have two buttons side by side at the top and below them a fragmentContainerView. Initially the fragmentContainerView doesn't have any fragment.
Now I have a viewModel where I am emitting a range of int values in a loop with 1 or 2 seconds delay with all three flow types. And I have consumers of the values in MainActivity, fragmentA and fragmentB (fragmentB has collectLatest in all three flows when collecting). Clicking button1 attaches fragmentA and Button2 attaches fragmentB.
Now what happens after the values are started emitting suppose initially from 0. The mainActivity starts receiving as soon as the values are emitted. Then when I click button1, fragmentA starts receiving from initial value 0. After sometime I click button2 which removes fragmentA and attaches fragmentB, now fragmentB starts receing from value 0 which has collectLatest. Again if I click button1, fragmentA starts receiving from initial value 0.
I can understand that when the fragments are not visible they should not receive any values. But I want to understand is this the intended behaviour like whenever a new fragment is coming visible its receiving from initial value instead of having collectLatest which did not work. Am I doing anything wrong or why is it happening like this? Are the previous initial values stored in some form of cache? and if I somewhere want to get the current latest value when the view is visible, in what way can I do it? Guidance with some sample code will help. Thank you
Fixed the problem:
Actually I made a mistake by creating new instances of the viewModel in fragments, and it was the viewModel where the values were getting emitted. Fixed it by getting the MainActivity's viewModel instance everywhere.
Sounds like you are using cold flows instead of hot flows.
The behavior of cold flows is that each new collector gets values starting from the very beginning (the flow producer starts a new production process for each collector). For example, if you use the flow Flow builder, like this:
val flow = flow {
for (i in 1..3) {
emit(i)
delay(100)
}
}
Then each time a coroutine calls collect on it, that coroutine will get a fresh new stream of values, starting from the beginning of the above lambda function.
With a hot flow, the behavior depends on the implementation. Channel-based flows fan out, which means no two collectors will ever receive the same value. For each value emitted, only one collector will receive it. SharedFlows can have a buffer that replays up to a certain number of past values for every collector. A StateFlow behaves like a SharedFlow with replay value of 1. Each new collector can only collect the most recent value followed by any further latest values, and if it is slower at collecting than values are being produced, it will skip values.
The generally recommended type of flow to use in a ViewModel that fits most uses is a SharedFlow with a replay buffer of 1, and if based on an upstream flow using shareIn, a SharingStarted of WhileSubscribed(5000). This is a hot flow, but new subscribers get the most recently emitted value from the replay. So if the screen is rotated, the most recent value is still in memory and can be immediately displayed in the UI. The SharingStarted.WhileSubscribed(5000) allows it to stop collecting from the upstream flow when there are no more views on screen collecting from it, but the 5 second buffer waits to make sure it's not just a screen rotation causing a very temporary lack of subscribers.
Related
I'm writing an Android app that takes screen-touch MotionEvents and converts them into a StateFlow, which is then collected by a Jetpack Compose function that draws them on the screen.
The MotionEvent capture (based around Modifier.motionEventSpy) works as expected, but the resultant Flows frequently don't make it to the Compose function, causing errors, if they're too close together in time.
It happens when two MotionEvents fire almost simulaneously: MotionEvent.ACTION_DOWN and the first MotionEvent.ACTION_MOVE happen in very rapid succession, and perhaps 20 or 30 per cent of the time, the MotionEvent.ACTION_DOWN Flow emission is never collected by the Compose function.
It doesn't happen all the time, or even most of the time, but it happens enough to be a problem.
(Another example of Flow emits not being collected: I've also noticed that, when I have an event listener on exoPlayer, emiting Flows when the exoPlayer state changes, only the second exoPlayer state change ever gets collected if two state changes happen near-simultaneously. For instance, when a media file is played to the very end, exoPlayer's onIsPlayingChanged listener fires at the same time as exoPlayer's onPlaybackStateChanged. If I have both of those listeners emiting on the same StateFlow, only one of them will ever be collected.)
So I'm thinking Kotlin Flows use some sort of mutex lock to prevent two emits/value changes happening at the same time. Is that correct?
And if that's the case, is there anything I do to ensure every Flow.emit or Flow.value = change makes it to the other side??
Are some types of Flows better than others when it comes to concurrency handling?
That is the expected behaviour of state flow. Sadly, the documentation points it out only on the testing section:
https://developer.android.com/kotlin/flow/test#stateflows
Note that this stream of values is conflated, which means that if values are set in a StateFlow rapidly, collectors of that StateFlow are not guaranteed to receive all intermediate values, only the most recent one.
Can you try using flow { } (I inow is cumbersome)? Or maybe can you try with channels (yep you loose the flow advantages)?
I have this code in an activity SignInActivity:
signInButton.setOnClickListener{
val query: HashMap<String, String> = HashMap()
query["email"] = signInEmail.text.toString()
query["password"] = signInPassword.text.toString()
signInViewModel.getAuthToken(query)
signInViewModel.signInResponse.observe(this, {
response-> when(response){
is NetworkResult.Success ->{
response.data?.let { Toast.makeText(this, it.access, Toast.LENGTH_SHORT).show()}
}
is NetworkResult.Error ->{
Toast.makeText(this, response.message.toString(), Toast.LENGTH_SHORT).show()
}
is NetworkResult.Loading -> {
}
}
})
}
Let's suppose in the first try I wrote my password wrong and it only runs once, but then after that if I click it again it runs multiple time by creating multiple toasts in this example.
Like #gpunto says, you're adding a new Observer every click, so they're stacking up and each one fires when the LiveData updates.
But really, the observer doesn't have anything to do with the actual click anyway, it just receives updates to signInResponse and displays a thing. The click just calls getAuthToken with the current query. If doing that happens to cause a signInResponse update, then you have everything wired up to react to that event. But the Activity doesn't need to know how all that stuff works, or be written so one thing follows another.
That's a reactive pattern, where your UI is really just sending events (like getAuthToken when there's a click) and then reacting to other events so it can display them. By separating these things, you get a simple system that Just Works, and can react to updates no matter what caused them (e.g. a click, or restoring state) without having to write code to handle each case.
That said, this is a slightly tricky case because you have an event you want to consume. If you just set up that observer on signInResponse, it will fire every time you get a value for that LiveData. And that includes when the Activity is recreated (e.g. on rotation), observes the LiveData, and gets the current (last-set) value. Basically, if you show a Toast, the same Toast will appear every time the Activity is recreated. That would be fine for setting the current value on a TextView, but it's bad for a popup that should only appear once.
This is the current official recommendation for handling this situation. They're creating a UI state, which basically holds everything that needs to be displayed, including any popup messages (which acts like a queue, which is useful!). When the UI displays a message, it basically tells the ViewModel it's done so, and that handles removing the message from the state.
You could just implement this your own way, even if it's something simple like a clearResponse() function in your VM that clears the current value when you've seen it. It really depends on your app and what state you need to maintain. Here's some other examples from the Android devs - but like it says at the top, this advice is deprecated following the recommendations I linked earlier
I have a screen listening for a data class that contains everything I need. ScreenState. Whenever the user press a button I send the event to a ViewModel. This specific event is just getting the intent and setting on the ScreenState parameter like this.
screenStateFlow.emit(
ScreenState(
Intent(...)
)
)
What happens there is, first time works (User leaves the app and then comeback to the app). When user comebacks to app and there's not any data from the intent and want them to be able to start an intent again. So it does the same action.
Triggers a specific event which gets the intent and sets on the ScreenState parameter and this value is emited, again
And here lays the problem. Value is the same. So compose doesn't recompose itself.
And this solution works. You could say that I don't need all of this and it could work by just starting the intent without having to go through the event process and etc.. But I want it that way (unless I don't find a proper solution)
screenStateFlow.emit(
ScreenState(
Intent(...),
!triggerRecompose
)
)
Is there any better solution?
Edit: Someone having the same issue as me, the provided answer didn't work. I've already tried the MutableState and the State from compose in ViewModel. Didn't work
I had a similar issue in which I wanted to trigger a snackbar even if the value is repeated.
I solved it by adding a variable parameter to my message object (such as timestamp or Math.random()).
In this way, even if the message content is the same, the Message object is different and it triggers a state change.
I have a data load method that returns LiveData. This LiveData is then passed through the Repository and ViewModel and its contents are viewed in the fragment. This happens in the onCreateView method. The problem is that the data from the database is displayed on the screen after a split second and the effect of the data appearing after starting this window is created. How can you prevent such a delay?
It is necessary for the user to immediately see this data as if it was hardcoded on the screen.
#Transaction
#Query("SELECT * FROM data")
fun getData(): LiveData<List<Data>>?
Well that is how LiveData works. It releases your UI to continue rendering whats happening (loading the new fragment) and only updates the fields once data is loaded.
To stop this, you will have to slow down the UI transition by adding animations etc, but if a users phone is slow it might not be sufficient..
Alternatively you could design a loading screen and gracefully deal with the loading delay.
What is the equivalent of getStickyEvent() from EventBus in RxJava.
I would like to subscribe to observables from "screens" that are not in
the foreground/not active, but at anytime very well may pop in.
If events are continuously happening, I want these "screens" to receive them the next time
they are active/in the foreground.
Edit:
It sounds like I should have a replaySubject, and then when the "Screen" comes
to the foreground subcribe to it.....?
BehaviorSubject – emits the last emitted item when subscribed to,
then continues to emit items from the source observable
You already gave the answer yourself but just to confirm: Yes, you would use either BehaviorSubject or ReplaySubject.
After a new subscriber subscribes, they will both emit to that subscriber all items they receive from then onwards. However, each has a little extra beyond that:
BehaviorSubject will always start that sequence by immediately emitting the (one) most recent of the items it has received before the subscriber subscribed, if there was any. If there was none it will emit a default item if one was provided when it was created.
ReplaySubject will always start that sequence by immediately emitting (some or) all of the items it has recevied since its creation in the order that it received it. ReplaySubject can be initialized to limit the number of items it keeps in the cache for later subsribers, or to limit the amount of time that it will keep items in the cache. But (as far as I know), you cannot provide a default value if using a ReplaySubject.
Then, calling
subject.subscribe(new Subscriber<YourEventClass>() {
// implement Subscriber methods here
});
would be more or less equivalent to:
eventbus.registerSticky(this);
and having this implement the callbacks for the EventBus.
Synchronous vs Asynchronous
Note, though, that subscribing like this still makes the delivery of items from the subject asynchronous (like register/registerSticky), as you are in both cases only handing over some callback methods and are not waiting right there for the result to be returned.
I have not used the greenrobot EventBus myself but it seems that getStickyEvent() is synchronous/blocking.
If you want blocking behavior you would have to - instead of subscribing to it - convert the subject to a blocking observable (with subject.toBlocking()).
See here for more on blocking observables:
https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators
but basically you can then transform them to an iterable, or just get the latest item, or a number of other things.