Reputation: 1170
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
Upvotes: 3
Views: 1132
Reputation: 392
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.
Upvotes: 0
Reputation: 89528
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:
... and everything else by Roman Elizarov on his blog, really :)
Upvotes: 3