I have a function to make network calls. It can be called multiple times at the same time and each call runs concurrently.
getDataTask() // it is subscribed on the background thread and observed on the main thread
.subscribe(
{ result -> onResult(result) },
{ onError() }
)
.addToDisposables()
I am able to retrieve the data without any problems. onResult function updates the MutableLiveData field in the ViewModel as
private val _data = MutableLiveData<Data>()
val data: LiveData<Data> get() = _data
private fun onResult(result: Data) = _data.post(result)
And the field is set to be observed in the Fragment's onViewCreated method as
viewModel.data.observe(viewLifecycleOwner, Observer { data -> // DO SOMETHING })
When the back-to-back concurrent calls succeed and try to update the _data field, I noticed that the observer does not observe some of the results. For example, there are 4 concurrent calls start at the same time then they try to post their results and 1 or 2 of the results are not observed. How can I tackle that issue? Any help would be appreciated.
The problem as I believe occurs due to the nature of LiveData. LiveData being an Observable ViewHolder will simply emit only the last value that it processed.
Now I understand you are doing concurrent calls and there is nothing wrong with that. If you want to save execution time in networking then that is the way to go. However, if you want this concurrency to work with your LiveData instance, and you want your LiveData to emit each and every value it process as soon as a concurrent method successfully returns, then you might be using LiveData wrong.
What you are seeking is LiveData's Observable nature. You get the lifecycle aware part bundled with it for free, but then again its not your regular Observable, but a special one which only retains the last value.
In your case, where you want to process each and every value, you are better with using RxJava's Observable. You can make it lifecycle aware as well, by subscribing & unsubscribing in your activity or fragment. You have so many operators in RxJava that one of them will certainly help you, couple that I can think are switchMap and Map.
Related
I would like to ask you why does it work?
Normally when I used collectLatest with flow my data wasn't collected on time and the return value was empty. I have to use async-await coroutines, but I have read it blocks main thread, so it is not efficient. I've made my research and find the solution using sharedflow.
Previously:
suspend fun getList: List<Items> {
CoroutineScope(Dispatchers.Main).launch {
async {
flow.collectLatest {
myItems = it
}
}.await()
}
return myItems
}
or without await-async and it returns emptyList
now:
suspend fun getList: List<Items> {
val sharedFlow = flow.conflate().shareIn(
coroutineScopeIO,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
return sharedFlow.first()
}
conflate means:
Conflates flow emissions via conflated channel and runs collector in a separate coroutine. The effect of this is that emitter is never suspended due to a slow collector, but collector always gets the most recent value emitted.
I'm not sure I understand it clearly. When I conflate flow, I just create seperate coroutine to emit what will be inside my another function as in my example shareIn().first() and using this variablesharedFlow which is suspended so will give the same effect I made asnyc-await, but in that case I do not block main thread, but only my exact *parentCoroutine-or-suspendFunction?
SharingStarted.WhileSubscribed()
It just means to start emit when subcribed.
conflate() has nothing to do with why this is working. The separate coroutine it talks about is run under the hood and you don't need to think about it. It's just to make sure your flow never causes the upstream emitter to have to wait for a slow collector, and your collector skips values if they are coming faster than it can handle them. conflate() makes it safe to have a slow collector without a buffer.
In your first code block, you are launching a new coroutine in a new CoroutineScope, so it is not a child coroutine and will not be waited for before the function returns. (Incidentally, this new coroutine will only finish when the Flow completes, and most types of Flows never complete.)
In the second code block, you are calling first() on the Flow, which suspends and gets the next value emitted by the flow and then returns that value without waiting for the Flow to complete.
Some other notes:
You should never use async { /*...*/ }.await() where await() is called immediately on the Deferred, because it is just a more convoluted version of withContext(/*...*/) { /*...*/ }.
It's a code smell to create a CoroutineScope that you never assign to a property, because the point of creating a scope is so you can manage the scope, and you obviously aren't managing it if you have no reference to it to work with.
You said you are worried about blocking the main thread, but nothing in the code you showed looks suspicious of blocking the main thread. But it's possible your flow that you are basing this on has blocking code in it. By convention it shouldn't. If that flow blocks, you should use the flowOn(Dispatchers.IO) operator on it at the source so downstream users don't have to worry about it.
Although your code worked, it doesn't make sense to create a SharedFlow in a function and immediately collect from it. It's not being shared with anything! Your code could be simplified to this equivalent code:
suspend fun getList: List<Items> {
return flow.first()
}
Why we should use suspend?
Are they used only to restrict a function not to be used outside coroutine scope or other suspend functions?
Are they used only to restrict a function not to be used outside co routine scope or other suspend functions?
No. The main purpose of a suspend function is to suspend a coroutine it is called in to eliminate callback hell. Consider the following code with a callback:
fun interface Callback {
fun call()
}
fun executeRequest(c: Callback) {
// ... create new Thread to execute some request and call callback after the request is executed
c.call()
}
On the caller side there will be:
executeRequest {
// ... do sth after request is competed.
}
Imagine you need to make another request after that one:
executeRequest {
// make another request
executeRequest {
// and another one
executeRequest {
// and another one ...
executeRequest {
}
}
}
}
That is a Callback Hell. To avoid it we can get rid of a Callback code and use only suspend functions:
// withContext(Dispatchers.IO) switches a coroutine's context to background thread
suspend fun executeRequest() = withContext(Dispatchers.IO) {
// just execute request, don't create another Thread, it is already in the background Thread
// if there is some result, return it
}
And on the caller side if there are a couple of requests, that should be executed one by one, we can call them without the Callback Hell by launching a coroutine and calling those request in the coroutine:
viewModelScope.launch {
executeRequest()
executeRequest()
executeRequest()
}
If you wonder what it does, I guess you will find the answer in this near-duplicate question's answer.
It basically allows to use the syntax of synchronous calls to call an asynchronous function.
If the question is "Why use async programming?", then there are probably plenty of resources explaining this on the internet. It usually allows to use threads more effectively and avoids using too many resources.
Now as to why you would want to use suspend to do that instead of callbacks, there is probably several reasons. Probably the biggest of them is to avoid the infamous "callback hell" (well known in JS): using actual callbacks creates nesting in the code. It makes the language harder to manipulate because you can't use local variables or loops as easily as with regular sequential code. With suspend functions, the code reads sequentially even though some asynchronous mechanisms are used behind the scenes to resume executing a piece of code at a later point.
Another big reason to use suspend (and more precisely the coroutines library in general) is structured concurrency. It allows to organize your asynchronous work so you don't leak anything.
Under the hood, suspend functions provide the extra functionality (involving a hidden Continuation object) that allow their code to be stopped and resumed later from where it left off, instead of the typical limitation that all function calls in a function are called in sequence and block the thread the entire time. The keyword is there to make the syntax very simple for doing a long-running task in the background.
Three different benefits of this:
No need for callbacks for long-running tasks ("callback hell")
Without suspend functions, you would have to use callbacks to prevent a long-running task from blocking the thread, and this leads to complicated-looking code that is not written in sequential order, especially when you need to do a number of long-running tasks in sequence and even more-so if you need to do them in parallel.
Structured concurrency
Built-in features for automatically cancelling long-running tasks. Guarantees about shared state being correct whenever accessed despite thread switching.
No need for state machines for lazy iterators
A limited set of suspend functions can be used in lazy iterator functions like the iterator { } and sequence { } builders. These are a very different kind of coroutine than the ones launched with CoroutineScopes, because they don't do anything with thread switching. The yield() suspend function in these builders' lambdas allows the code to be paused until the next item is requested in iteration. No thread is being blocked, but not because it's doing some task in the background. It instead enables code to be written sequentially and concisely without a complicated state machine.
In MyViewModel a MutableStateFlow is used to transmit events to the fragment.
When the value of the MutableStateFlow is changed the earlier values are being overwritten inside the coroutine. So never received by fragment.
internal class MyViewModel(application: Application) : AndroidViewModel(application) {
private val myMutableStateFlow = MutableStateFlow<MySealedClass>(MySealedClass.Dummy1())
private fun getData() {
viewModelScope.launch {
//yield()
myMutableStateFlow.value = MySealedClass.Dummy2()
myMutableStateFlow.value = MySealedClass.Dummy3()
}
}
}
internal class MyFragment : Fragment(){
private var uiStateJob: Job? = null
override fun onStart() {
super.onStart()
uiStateJob = lifecycleScope.launch {
myViewModel.getUiFlow().collect {
//do something
}
}
}
}
If yield() is commented Dummy2 event is never received by the Fragment. Dummy 3 is received though.
If yield() is uncommented Dummy2 & 3 are both received.
If the state values are changed outside the coroutine then both Dummy2 and Dummy3 are received.
I need to predictably receive all events in my fragment.
Is there a proper reasoning for this behaviour?
StateFlow is meant to represent a state. Each event is technically a new up-to-date state value, making the previous states obsolete. This type of flow is for situations when only the latest state matters, because its events are conflated. From the docs:
Updates to the value are always conflated. So a slow collector skips fast updates, but always collects the most recently emitted value.
Edit in response to your comment: yield() is a suspend function that forces the suspension of the current coroutine. Therefore it gives a chance to the other coroutine to progress until its next suspension point, this is why in that case the collect is "ready" before the first value is set (and emitted).
However you shouldn't rely on that because it's brittle: if the other coroutine gets modified and has extra suspension points by calling other suspend functions, it might not reach the collect call, and you would be back to the other behaviour.
If you consistently need all events, you have several options:
Switch to a cold flow, which will only start when you collect
Use a Channel (with or without buffer)
Use a SharedFlow and trigger the events start by using onSubscription
which is better between these two
1) Using coroutines in Viewmodel to fetch data from network and updating View using live data?
2) Using coroutine from View to call suspend function in viewmodel that fetches data from network?
Another question
Should we be using livedata for use cases where we need to update UI only once from backend, like data won't be changing while user is on that screen
I'm voting for (1), using LiveData for that final step of moving the data from the ViewModel to the View.
Here's why: if you start a coroutine in the UI which fetches the data through your ViewModel...
You'll end up with a suspending call like getData() in the View. Whether that's a Fragment or an Activity, that coroutine will deliver the result to only that specific instance. If it gets recreated due to a configuration change, you'll need to fetch again in the new instance.
If you're handling cancellation for your coroutines (which you probably should be), a configuration change will mean that any work you've already done in the ViewModel and around the network will be lost (e.g. any progress on a long running network call), since your coroutine is cancelled when your View is destroyed.
If you're not cancelling your coroutines when the View is destroyed, your data fetching function may attempt to update the UI in a View that doesn't exist any more if when completes.
In comparison, if you start coroutines in your ViewModel and then place the result in a LiveData:
Your fetches can continue across configuration changes due to the ViewModel's longer lifespan.
You can cancel coroutines when your screen is closed for good (in onCleared) instead of at configuration changes.
LiveData observers will only be called when the View exists and is in an active (foreground) state, so you don't have to worry about getting results when your View isn't ready for it (or doesn't exist anymore).
When your View is recreated, the new instance can start observing the LiveData and receive the value that's already loaded. Or, if your data is still loading, it will even eventually receive the result of the network call that was started for a previous View instance.
I'd like to create an observable in a singleton class that manages state (i.e. it stores an auth token). I'd like my android app/activity to subscribe to an observable that will emit an update every time the state (auth token) is updated. How do I do this? All examples I've seen show how you can create a self contained observable that completes immediately or after subscription.
Thanks for your help!
You need a BehaviorSubject.
BehaviorSubject<State> rxState = BehaviorSubject.create(initialState);
// update state
rxState.onNext(newState);
// observe current state and all changes after
rxState.subscribe(...);
If you want to set the state from multiple threads concurrently, you need this as the first line.
Subject<State, State> rxState = BehaviorSubject.create(initialState).toSerialized();