Coroutine never calls API after once API is failed - android

In my viewmodel i've a function which uses coroutine to call an API.
fun loadPosts(){
GlobalScope.launch(coroutineContext){
changeState(load = true)
val list= withContext(Dispatchers.IO) {
apiService.getProfile(AUTH)
}
changeState(false,false,null)
showResult(list)
}
}
Everytime i click on a button, this function is fired, API is called and I get valid response. But once my api get Exception like 500 or Http 401 Unauthorized, then again when I hit the button, Coroutine is never called and seems it returns the again error message from cache.
For a use case:
I clicked on button -> Api is called -> got success response
Again I clicked on -> Api is called -> got success response
I disconnected my internet from phone
I clicked on button -> Api is called -> got exception something like ConnectionError
I connected my phone to internet
I clicked on button -> Api is not called -> got exception something like ConnectionError
Now even my phone has valid internet connection, I press on button, instead of calling api and wait for response, It gives me previous failed response again and again.
Earlier I was using Rxjava and I didn't face any such issue in that. I am new to coroutines so if anyone have any suggestion you are most welcome

Whenever you start a coroutine with a coroutine builder such as launch, you need to start it in a given CoroutineScope - this is enforced by the function being defined as an extension on CoroutineScope. This scope contains a CoroutineContext, which will define how the coroutine is executed.
Based on the comment above, I assume you are using roughly this setup:
abstract class BaseViewModel : ViewModel(), CoroutineScope {
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCleared() {
coroutineContext.cancel()
}
}
By using GlobalScope.launch(coroutineContext), you're actually overriding everything provided by GlobalScope with the context parameter. Plus, since your ViewModel itself is a scope already, you don't need to launch in GlobalScope in the first place. You can simply write down launch within the ViewModel with no scope specified (essentially this.launch {}) and no context passed to it, as it will get the one from the scope anyway.
The other issue is that you're using a regular Job as a part of your CoroutineContext. This Job becomes a parent for every coroutine you start, and whenever a child coroutine fails, such as on a network error, the parent Job gets cancelled too - meaning that any further children you attempt to start will fail immediately as well, as you can't start a new child under an already failed Job (see the Job documentation for more details).
To avoid this, you can use a SupervisorJob instead, which can also group your coroutines together as children, and cancel them when the ViewModel is cleared, but won't get cancelled if one of its children fail.
So to sum up the fixes to make at the code level quickly:
Use a SupervisorJob in the ViewModel (and while you're there, create this combined CoroutineContext just once, by directly assigning it, and not placing it in a getter):
override val coroutineContext: CoroutineContext = Dispatchers.Main + SupervisorJob()
Launch your coroutines in the scope defined in the ViewModel instead of in GlobalScope:
abstract class BaseViewModel : ViewModel(), CoroutineScope {
// ...
fun load() {
launch { // equivalent to this.launch, because `this` is a CoroutineScope
// do loading
}
}
}
You may also want to consider having your ViewModel contain a CoroutineScope instead of implementing the interface itself, as described here an discussed here.
There's a lot of reading to do on this topic, here are some articles I usually recommend:
https://medium.com/#elizarov/coroutine-context-and-scope-c8b255d59055
https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc
https://proandroiddev.com/demystifying-coroutinecontext-1ce5b68407ad
... and everything else by Roman Elizarov on his blog, really :)

One more option.
If you use androidx.lifecycle:lifecycle-extensions:2.1.0, ViewModel now has viewModelScope extension property use SupervisorJob default. And it will be cleared automatically when ViewModel cleared.

Related

Parent and Child Coroutines not getting cancelled

I have a Parent Coroutine with a Child Coroutine like this:
val exceptionHandler = CoroutineExceptionHandler {_,e -> println("exception $e")}
val scope = CoroutineScope(Dispatchers.Main)
mainCoroutineJob = scope.launch(exceptionHandler){
val data = withContext(Dispatchers.IO){
val data = getData()
data
}
data?.let{
// do something with data
}
}
When I try to cancel both the parent and child coroutines using this:
mainCoroutineJob.cancel("Coroutine Cancelled")
mainCoroutineJob.cancelChildren(CancellationException("Coroutine Cancelled"))
the code inside withContext, keeps on running.
May I know why? And how can we cancel the withContext as well?
withContext is a suspend function, not a coroutine builder, therefore there's no child coroutine. The reason why it's not stopping when the job is cancelled is because it is not cooperative. You need to make your getData cooperatively cancellable. I assume it is already a suspend function, if so, then you just need to check at critical points whether the job that is running that suspend function still active and proceed only if it is. You can check it by using coroutineContext.isActive inside of a suspend function.
A critical point might be a loop or if there's no loop but a function is still doing some heavy processing you can divide your function into chunks and then before proceeding into processing the next chunk you check whether the suspend function's job is still active or not, and proceed only if it is active.

How to know when job from viewModel is done

I am trying to figure out how jobs with coroutines work. Basically, I want to launch this coroutine from FirstFragment and after that navigate to SecondFragment and get notified when this job is done. I call getData() in FirstFragment onViewCreated() and navigate to SecondFragment. Whether I write getData().isCompleted or getData().invokeOnCompletion { } in SecondFragment nothing happens. I don't know if I am missing something or not starting job correctly or something else.
private val _data = MutableStateFlow<GetResource<String>?>(null)
val data: StateFlow<GetResource<String>?> = _data
fun getData() = viewModelScope.launch {
repository.getData().collect {
_data.value = it
}
}
A Flow from a database never completes because it is supposed to monitor the database for changes indefinitely. It only stops when the coroutine is cancelled. Therefore the Job that collects such a Flow will never complete. Also, if you call getData() on the repo again, you are getting a new Flow instance each time.
Regardless of what you're doing, you need to be sure you are using the same ViewModel instance between both fragments by scoping it to the Activity. (Use by activityViewModels() for example.) This is so the viewModelScope won't be cancelled during the transition between Fragments.
If all you need is a single item from the repo one time, probably the simplest thing to do would be to expose a suspend function from the repo instead of a Flow. Then turn it into a Deferred. Maybe by making it a Lazy, you can selectively decide when to start retrieving the value. Omit the lazy if you just want to start retrieving the value immediately when the first Fragment starts.
// In the shared view model:
val data: Deferred<GetResource<String>> by lazy {
viewModelScope.async {
repository.getData() // suspend function returning GetResource<String>
}
}
fun startDataRetrieval() { data } // access the lazy property to start its coroutine
// In second fragment:
lifecycleScope.launch {
val value = mySharedViewModel.data.await()
// do something with value
}
But if you have to have the Flow because you’re using it for other purposes:
If you just want the first available value from the Flow, have the second Fragment monitor your data StateFlow for its first valid value.
lifecycleScope.launch {
val value = mySharedViewModel.data.filterNotNull().first()
// do something with first arrived value
}
And you can use SharedFlow so you don’t have to make the data type nullable. If you do this you can omit filterNotNull() above. In your ViewModel, it’s easier to do this with shareIn than your code that has to use a backing property and manually collect the source.
val data: SharedFlow<GetResource<String>> = repository.getData()
.shareIn(viewModelScope, replay = 1, SharingStarted.Eagerly)
If you need to wait before starting the collection to the SharedFlow, then you could make the property lazy.
Agreed with #Tenfour04 's answer, I would like to contribute a little more.
If you really want to control over the jobs or Structured Concurrency, i would suggest use custom way of handling the coroutine rather than coupled your code with the viewModelScope.
There are couple of things you need to make sure:
1- What happen when cancellation or exception occurrs
2- you have to manage the lifecycle of the coroutine(CoroutineScope)
3- Cancelling scope, depends on usecase like problem facing you are right now
4- Scope of ViewModel e.g: Either it is tied to activity(Shared ViewModel) or for specific fragment.
If you are not handling either of these carefully specifically first 3, your are more likely to leaking the coroutine your are gurenteed gonna get misbehavior of you app.
Whenever you start any coroutine in Custom way you have to make sure, what is going to be the lifecycle, when it gonna end, This is so important, it can cause real problems
Luckily, i have this sample of CustomViewModel using Jobs: Structured Concurrency sample code

calling coroutine function from activity returns `should be called only from a coroutine or another suspend function`

I'm trying to use coroutines in my code since in one of my function, I need to do multiple network calls and wait for its result. Below is a portion of my activity code:
class SellerDeliveryRegister : AppCompatActivity() {
lateinit var sellerDeliveryVM:SellerDeliveryVM
sellerDeliveryVM = ViewModelProviders.of(this).get(SellerDeliveryVM::class.java)
var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url)
}
And below is a portion of my sellerDeliveryVM ViewModel code:
class SellerDeliveryVM: ViewModel() {
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): Unit = withContext(Dispatchers.IO){
var uploadMotorImage1Result = "-1"; var uploadMotorImage2Result = "-1";
viewModelScope.launch(Dispatchers.IO) {
uploadMotorImage1Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage1Url)
}
uploadMotorImage2Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage2Url)
}
return#launch;
}
return#withContext
}
}
Please take note that previously uploadVehiclesImages is a 'normal' function that doesn't use coroutine, so now I'm converting it to use coroutine.
Below are the problems I'm facing:
Line var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url) inside my SellerDeliveryRegister class returns this error:
Suspend function 'uploadVehiclesImages' should be called only from a coroutine or another suspend function
Initially I want to return Boolean from uploadVehiclesImages, so I have return true in place of the return#launch and return false in place of the return#withContext, but then I will get the error return is not allowed here, and Android Studio suggested me to make the changes above, although I really have no idea what the changes meant there.
So what should I do to fix this problem 1, and can anyone enlighten me more on what's really happening on the problem 2?
So what should I do to fix this problem 1
Remove the property. uploadVehiclesImages() returns Unit; there is no value in having Unit in a property. If your objective is to call uploadVehiclesImages() when the viewmodel is created, put a call to it in an init block, wrapped in a suitable coroutine launcher (e.g., viewModelScope.launch {}).
This assumes that you are going to keep the function in its current form — your next question suggests that this function may not be the right solution.
Initially I want to return Boolean from uploadVehiclesImages,
More importantly, you seem to want it to return values more than once. That is not how functions work in Kotlin. One call to uploadVehiclesImages() can return one Boolean value, but not one now and one sometime in the future.
If you want to be emitting a stream of results, a suspend function on its own is not the correct solution. For example, you could:
Use LiveData, with the suspend function updating the backing MutableLiveData, or
Use a function that returns a StateFlow or SharedFlow
For part 1, you cannot use a coroutine to initialize a property. Coroutines return some time in the future, but properties have to be initialized immediately at class instantiation time. You'll have to change the strategy so you launch a coroutine that calls the suspend function, and then does something with the result when it arrives.
For part 2, you have an awkwardly composed suspend function. A proper suspend function typically isn't launching other coroutines unless it is using them to break down multiple simultaneous asynchronous actions and then waiting for them.
The convention for a suspend function is that it is safe to call from any Dispatcher. It's not proper to be sending off these background actions by launching a coroutine in a specific coroutine scope. Usually, a coroutine that calls a suspend function should not have to worry that the suspend function is going to launch some other coroutine in another scope, because this breaks support for cancellation.
Also, you can use async instead of launch to run suspend functions that you need a result from. That will avoid the awkward variables you've created to store the results (and you neglected to wait for).
Assuming you want to return both of these image results, you'll have to wrap them in another class, such as List. So your function could look like below. It returns something, not Unit. It uses aysnc to run the two requests simultaneously.
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): List<ImageUploadResult> {
return listOf(uploadMotorImage1Url, uploadMotorImage2Url)
.map { aysnc { NetworkRepository.instance!!.uploadFile(it) }
.awaitAll()
}
I just put ImageUploadResult to stand in for whatever this uploadFile function returns. Maybe it's just Boolean.
Whenever you do want to call it, you would use either lifecycleScope (from an Activity or Fragment) or viewModelScope (from a ViewModel) to launch a coroutine that calls it. For example:
fun doUpload(url1: String, url2: String) {
lifecycleScope.launch {
val results = uploadVehiclesImages(url1, url2)
// do something with results here
}
}

Kotlin difference between CoroutineScope and withContext

To change the thread in a function I use either CoroutineScope or withContext. I don't know's the difference, but with CourineScope I can also use a handler.
Examples:
private fun removeViews(){
CoroutineScope(Main).launch(handler){
gridRoot.removeAllViews()
}
}
private suspend fun removeViews(){
withContext(Main){
gridRoot.removeAllViews()
}
}
I call this function from a coroutine that works on background thread (IO). Is any more appropriate than the other?
These two are actually radically different and you just happen to have a use case where you don't experience the difference:
CoroutineScope(Main).launch(handler){
This launches a concurrent coroutine that goes on independently.
withContext(Main){
This is a function that completes only when the code inside it completes, and returns its result. This is the way you should be doing it.
The first approach, with CoroutineScope, has another deficiency in that it circumvents structured concurrency. You create an ad-hoc coroutine scope that has no parent and thus won't be automatically cleaned up if it takes a longer time to complete and your GUI is dropped (user navigates away from the current Activity).
You should actually never use the CoroutineScope(Main) idiom, I don't think there's a single instance where it would be appropriate. If you explicitly want to avoid structured concurrency, it is still better and cleaner to write
GlobalScope.launch(Main + handler) {
and has pretty much the same effect.
If you want a concurrent coroutine that fits into structured concurrency, use
fun CoroutineScope.removeViews() {
launch {
gridRoot.removeAllViews()
}
}
Note I removed the handler argument, a child coroutine ignores it because it forwards any failures to its parent coroutine, which is exactly what you want. The parent coroutine should have an exception handler installed.
Technically both are same but when it comes to use case both are different and has big impact on the different use cases so be careful while using them
Coroutine Scope:
CoroutineScope is a starting Point of Coroutine. CoroutineScope can have more than one coroutine within itself, which makes coroutine hierarchy.
Lets think, Parent has more than one children. Think CoroutineScope is a parent and this parent can have more than one child which are also coroutines. These childrens are known as job
private val coroutineScope = CoroutineScope()
coroutineScope(IO).launch{
val childOne = launch(Main){}
val childTwo = launch(Main){}
}
see that childOne and childTwo? why we need these? because we can't directly cancel the coroutine there is no such way the coroutine can be cancelled directly, either the coroutine gets completed or it gets failed. But what if we wanna cancel it? in such cases we need job. But thing to be notice here these job children are totally associated with parent. And Parent is (IO) and childrens are (Main), this parent is started in IO Disptacher but when it comes to those childrens they are gonna switch to (Main) and do their thing but the parent will still be at (IO) switching the Dispatcher of childrens not gonna effect parent.
But what happens if something wrong happens to either of the children,
in that case we will watch this summit:
https://www.youtube.com/watch?v=w0kfnydnFWI
This summit about coroutine exception and cancellation. watch it, its amazing...
withContext:
What is withContext?
withContext should be inside any Coroutine or suspend fun because withContext itself is a suspending function.
withContext is use to switch the context in different situation
but how?
suspend fun fetchFromNetworkAndUpdateUI() {
withContext(IO){
println("Some Fake data from network")
}
withContext(Main){
//updating Ui
//setting that Data to some TextView etc
}
}
see the code, we are fetching the data asynchronously from network cause we don't wanna block the MainThread and then we switch the context, why? cause we can't update UI related stuff in IoDispatcher that's we have change the context to main with withContext(main){} and update the UI.
and there are other use cases like liveData, we are fetching the value using retrofit using IoDispatcher then in next step we have to set it to the liveData by using withContext(main){} cause we can't observe liveData's value in background thread.
yeah, I hope this helps. comment if there is any question.
From the Antonio Leiva article about coroutines:
The coroutine context is a set of rules and configurations that define
how the coroutine will be executed
withContext is a function that allows you to easily change the context of a suspending function, in order to be sure that that function is executed in a particular thread (E.g. Thread from IO pool). To do so you can force a suspending function to execute its body within a particular thread pool, for example:
suspend fun getAuthenticationStatus(): AuthenticationStatus = withContext(Dispatchers.IO) {
when (val result = repository.getAuthenticationStatus()) {
is Result.Success -> result.data
is Result.Error -> AuthenticationStatus.Unauthorized
}
}
This way, even if you're calling this suspending function from a UI scope (MainScope), you are 100% sure that the suspending function is executed in a worker thread and you can update the UI with the returned result in the main thread, such as:
MainScope().launch {
userIdentityVM.getAuthenticationStatus().run {
when (this) {
is AuthenticationStatus.Authenticated -> {
// do something
}
is AuthenticationStatus.Unauthorized -> {
// do something else
}
}
}
}
To sum up, by using withContext you can make your suspending function "Main Safe".
The difference between scope and context is basically the intended purpose.
To launch a coroutine you normally use launch coroutine builder, defined as an extension function on CoroutineScope.
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
// ...
): Job
The context specified as parameter on the coroutine scope is merged to coroutine scope by plus operator and takes precedence on the "default" context specified by coroutine scope. This way you can execute the code in a "parent" context. To go deep I suggest you this article by Roman Elizarov (Team Lead for Kotlin libraries #JetBrains).

Is the Kotlin Structured Concurrency [coroutine] model scoped to UI suitable for DB writes?

I'm specifically concerned about inserting user initiated data into the local database.
The following pattern is prevalent in examples (including from official sources, e.g. JetBrains, Google/Android) for using Kotlin coroutines in conjunction with [Android Architecture Components] ViewModels.
class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
fun thisIsCalledFromTheUI() = launch {
/* do some UI stuff on the main thread */
withContext(Dispatchers.IO) {
try {
/* do some IO, e.g. inserting into DB */
} catch (error: IOException) {
/* do some exception handling */
}
}
}
}
It's my understanding of the documentation that in the above example the coroutines started in the UI context (defined through coroutineContext) will be cancelled when the ViewModel is destroyed, but that the code in the withContext(Dispatchers.IO) block will get to run to completion.
But, before I go about refactoring my project from the (pre-1.0.0) globally scoped (launch/async) coroutine model, I feel I need to just have some things clarified:
Is my reading of the documentation correct? Or, will destruction of the viewmodel before the withContext(Dispatchers.IO) block runs to completion trigger cancellation of that job too? I.e. can this model be used for inserting data into my DB, or could some strange timing issue arise where the user hits back or otherwise causes the ViewModel owner to close that ends up losing the data?
I don't want to inadvertently introduce a timing bug because I misunderstood something and therefor converted my code to a model similar to the one shown above.
EDIT:
So, I decided to do a little test, and it seems to me that all those examples using this model to write to the database may have a fundamental bug.
Modifying the code to log what happens, as such:
class ChildViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
Log.d("onCleared", "Start")
_job.cancel()
Log.d("onCleared", "End")
}
fun thisIsCalledFromTheUI() = launch {
Log.d("thisIsCalledFromTheUI", "Start")
GlobalScope.launch(Dispatchers.IO) {
Log.d("GlobalScope", "Start")
delay(15000)
Log.d("GlobalScope", "End")
}
withContext(Dispatchers.IO) {
Log.d("withContext", "Start")
delay(10000)
Log.d("withContext", "End")
}
Log.d("thisIsCalledFromTheUI", "End")
}
}
Results in this, if you let it run to completion:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/withContext: End
D/thisIsCalledFromTheUI: End
D/GlobalScope: End
But, if you close the Fragment/Activity (not the app) before withContext ends, you get this:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/GlobalScope: End
Which indicates, to me at least, that you cannot use this to write non-transient data to the DB.
It's my understanding of the documentation that in the above example the coroutines started in the UI context (defined through coroutineContext) will be cancelled when the ViewModel is destroyed, but that the code in the withContext(Dispatchers.IO) block will get to run to completion.
This isn't a correct reading of the documentation. withContext doesn't start another coroutine, it just changes the current coroutine's context for the duration of its block. Therefore this coroutine will get cancelled, as well as all other coroutines you start without providing a new parent context that has a different job associated with it (or no job at all, like the GlobalScope).
However, your proposed idea to use the GlobalScope for persistent operations is just a local patch for the scenario you're testing, you're still not getting a guarantee it will run to completion. The user can exit the application completely and Android can kill the process.
Therefore, if your goal is building a truly robust application, you must accommodate the fact that, until the coroutine completes, no information was written to the DB. Hopefully you run the operation within a DB transaction that will automatically roll back if your program gets killed, otherwise it will be impossible to prevent inconsistencies.

Categories

Resources