我正在尝试使用View / ViewModel / UseCase / Repository模式进行基本的网络调用。主要异步调用是通过协程执行的,协程都使用Dispatchers.IO
启动。
首先,下面是相关代码:
ViewModel:
class ContactHistoryViewModel @Inject constructor(private val useCase: GetContactHistory) : BaseViewModel() {
// ...
fun getContactHistory(userId: Long, contactId: Long) {
useCase(GetContactHistory.Params(userId, contactId)) { it.either(::onFailure, ::onSuccess) }
}
}
GetContactHistory用例:
class GetContactHistory @Inject constructor(private val repository: ContactRepository) : UseCase<ContactHistory, GetContactHistory.Params>() {
override suspend fun run(params: Params) = repository.getContactHistory(params.userId, params.contactId)
data class Params(val userId: Long, val contactId: Long)
}
上面使用的基本UseCase类:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
operator fun invoke(params: Params, onResult: (Either<Failure, Type>) -> Unit = {}) {
val job = GlobalScope.async(Dispatchers.IO) { run(params) }
GlobalScope.launch(Dispatchers.IO) { onResult(job.await()) }
}
}
最后,存储库:
class ContactDataRepository(...) : SyncableDataRepository<ContactDetailDomainModel>(cloudStore.get(), localStore),
ContactRepository {
override fun getContactHistory(userId: Long, contactId: Long): Either<Failure, ContactHistory> {
return request(cloudStore.get().getContactHistory(userId, contactId), {it}, ContactHistory(null, null))
}
/**
* Executes the request.
* @param call the API call to execute.
* @param transform a function to transform the response.
* @param default the value returned by default.
*/
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Either.Right(transform((response.body() ?: default)))
false -> Either.Left(Failure.GenericFailure())
}
} catch (exception: Throwable) {
Either.Left(Failure.GenericFailure())
}
}
}
摘要:
将调试断点放在存储库中的catch{}
块中(直接在上方看),表明正在抛出android.os.NetworkOnMainThreadException
。鉴于两个协程都是在Dispatchers.IO
而不是Dispatchers.Main
(Android的主UI线程)的上下文下启动的,这很奇怪。
问题: 为什么会抛出上述异常,并且如何纠正此代码?
答案 0 :(得分:0)
标记函数suspend
使其不可挂起,您必须确保工作实际上在后台线程中进行。
你有这个
override suspend fun run(params: Params) = repository.getContactHistory(params.userId, params.contactId)
将其称为
override fun getContactHistory(userId: Long, contactId: Long): Either<Failure, ContactHistory> {
return request(cloudStore.get().getContactHistory(userId, contactId), {it}, ContactHistory(null, null))
}
这些都是同步的,您的suspend
修饰符在这里没有执行任何操作。
一个快速的解决方法是像这样更改您的存储库
override suspend fun getContactHistory(userId: Long, contactId: Long): Either<Failure, ContactHistory> {
return withContext(Dispatchers.IO) {
request(cloudStore.get().getContactHistory(userId, contactId), {it}, ContactHistory(null, null))
}
}
但是更好的解决方案是使用Coroutine适配器进行翻新。
答案 1 :(得分:0)
问题是您没有在任何地方创建协程。您可以为此使用高阶函数suspendCoroutine
。一个简单的示例如下:
private suspend fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return suspendCoroutine { continuation ->
continuation.resume(try {
val response = call.execute()
when (response.isSuccessful) {
true -> Either.Right(transform((response.body() ?: default)))
false -> Either.Left(Failure.GenericFailure())
}
} catch (exception: Throwable) {
Either.Left(Failure.GenericFailure())
})
}
}
您也可以使用很多方法。请注意,此函数永远不会引发异常。我认为这是有意的,但是如果要向上传播它以便例如将其包装在try-catch
块中,则可以使用continuation.resumeWithException(...)
。
由于此函数返回实际的协程,因此您的withContext(Dispatchers.IO)
应该可以正常工作。希望有帮助!