Reputation: 1783
I am trying to achieve is to have UI, IO and DEFAULT CoroutineScope within my BaseActivity activity. Then use these scopes for some operations.
Everything seems to work fine unless exception is thrown inside suspendCoroutine {}.
If no exception is thrown I can use ioScope multiple times without no problems. But if exception is thrown in Response.ErrorListener and I call loginAsync(), async won't be executed and coroutine will stuck at userDeferred.await().
I checked ioScope.isActive flag. Before Exception was thrown flag was set to true. After exception was thrown flag is set to false and I can exception in was thrown in scope.
I found that when I use instead of ioScope.async{ } function withContext(ioScope.coroutineContext){} exception will not break scope and it can be used again.
Could anyone please help me to resolve this issue. I could not find any help in documentations nor in blogs.
BaseActivity CoroutineScopes creation.
abstract class BaseActivity : AppCompatActivity() {
private val ioJob = Job()
private val defaultJob = Job()
private val uiJob = Job()
protected val ioScope: CoroutineScope
get() = CoroutineScope(Dispatchers.IO + ioJob)
protected val uiScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Main + uiJob)
protected val defaultScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Default + defaultJob)
final override fun finish() {
super.finish()
uiJob.cancel()
defaultJob.cancel()
ioJob.cancel()
getActivityTransitions().setFinishActivityTransition(this)
}
}
UserRepository Usage of ioScope from BaseActivity.
@Throws(LoginException::class)
suspend fun loginAsync(loginData: LoginData, context: Context): Deferred<User> {
return ioScope.async {
suspendCoroutine<User> { continuation ->
val jsonObjectRequest = HttpClient.createJsonObjectRequest(
"/users/me2",
loginData.toJsonString(),
Response.Listener {
val httpResponse : HttpResponse<User> = it.toString().jsonToObject()
continuation.resume(httpResponse.response)
},
Response.ErrorListener {
continuation.resumeWithException(LoginException(it))
}
)
HttpClient.getInstance(context).addToRequestQueue(jsonObjectRequest)
}
}
}
LoginActivity
private suspend fun performLogin() {
val loginData = LoginData(login_username_text_input.value.toString(), login_password_text_input.value.toString())
val userDeferred = UserServerRepository(ioScope).loginAsync(loginData,this@LoginActivity);
try {
val result = userDeferred.await()
login_username_text_input.value = result.company
//HomeActivity.startActivity(this@LoginActivity)
//finish()
}catch (loginException: LoginException){
login_username_text_input.value = loginException.message
}
}
LoginActivity button setup
loginButton.main_button.setAsyncSafeOnClickListener(uiScope, suspend {
performLogin()
})
setAsyncSafeOnClickListener implemetation
fun View.setAsyncSafeOnClickListener(uiScope: CoroutineScope, action: suspend () -> Unit) {
val safeClickListener = SafeClickListener {
uiScope.launch {
isEnabled = false
action()
isEnabled = true
}
}
setOnClickListener(safeClickListener)
}
Upvotes: 0
Views: 101
Reputation: 8422
Short answer: you have to use SupervisorJob()
instead of Job()
if you want to have robust scopes.
Long answer: here is great article about how error handling in coroutine scopes works https://proandroiddev.com/kotlin-coroutine-job-hierarchy-finish-cancel-and-fail-2d3d42a768a9
Upvotes: 1