Kotlin 协程处理错误和实现
Kotlin coroutines handle error and implementation
第一次使用协程。需要帮助。
这是我的流程:
Presenter 想要登录所以调用存储库接口。 Repository 实现 RepositoryInterface。
所以 Repository 调用 APIInterface。 APIInterface由APIInterfaceImpl实现。
APIInterfaceImpl最终调用了MyRetrofitInterface。
这是流程图:
Presenter -> Repository -> APIInterfaceImpl -> MyRetrofitInterface
收到登录响应后:
APIInterfaceImpl -> 存储库 -> 将数据存储在缓存中 -> 将 http 状态代码提供给 Presenter
这是我的代码:
RepositoryInterface.kt
fun onUserLogin(loginRequest: LoginRequest): LoginResponse
Repository.kt
class Repository : RepositoryInterface {
private var apiInterface: APIInterface? = null
override fun onUserLogin(loginRequest: LoginRequest): LoginResponse {
return apiInterface?.makeLoginCall(loginRequest)
}
}
APIInterface.kt
suspend fun makeLoginCall(loginRequest): LoginResponse?
APIInterfaceImpl.kt
override suspend fun makeLoginCall(loginRequest: LoginRequest): LoginResponse? {
if (isInternetPresent(context)) {
try {
val response = MyRetrofitInterface?.loginRequest(loginRequest)?.await()
return response
} catch (e: Exception) {
//How do i return a status code here
}
} else {
//How do i return no internet here
return Exception(Constants.NO_INTERNET)
}
}
MyRetrofitInterface.kt
@POST("login/....")
fun loginRequest(@Body loginRequest: LoginRequest): Deferred<LoginResponse>?
我的问题是:
- 我的方法在架构上是否正确?
- 如何在我的代码中传递 http 错误代码或没有互联网连接
- 我的解决方案还有更好的方法吗?
在本地范围内启动协程是一种很好的做法,它可以在生命周期感知 classes 中实现,例如 Presenter 或 ViewModel。您可以使用下一种方法来传递数据:
在单独的文件中创建 sealed
Result
class 及其继承者:
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
使 onUserLogin
函数可挂起并在 RepositoryInterface
和 Repository
中返回 Result
:
suspend fun onUserLogin(loginRequest: LoginRequest): Result<LoginResponse> {
return apiInterface.makeLoginCall(loginRequest)
}
根据以下代码更改APIInterface
和APIInterfaceImpl
中的makeLoginCall
函数:
suspend fun makeLoginCall(loginRequest: LoginRequest): Result<LoginResponse> {
if (isInternetPresent()) {
try {
val response = MyRetrofitInterface?.loginRequest(loginRequest)?.await()
return Success(response)
} catch (e: Exception) {
return Error(e)
}
} else {
return Error(Exception(Constants.NO_INTERNET))
}
}
为您的 Presenter
使用下一个代码:
class Presenter(private val repo: RepositoryInterface,
private val uiContext: CoroutineContext = Dispatchers.Main
) : CoroutineScope { // creating local scope
private var job: Job = Job()
// To use Dispatchers.Main (CoroutineDispatcher - runs and schedules coroutines) in Android add
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun detachView() {
// cancel the job when view is detached
job.cancel()
}
fun login() = launch { // launching a coroutine
val request = LoginRequest()
val result = repo.onUserLogin(request) // onUserLogin() function isn't blocking the Main Thread
//use result, make UI updates
when (result) {
is Success<LoginResponse> -> { /* update UI when login success */ }
is Error -> { /* update UI when login error */ }
}
}
}
编辑
我们可以在 Result
class 上使用扩展函数来替换 when
表达式:
inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
if (this is Error) action(this)
return this
}
class Presenter(...) : CoroutineScope {
// ...
fun login() = launch {
val request = LoginRequest()
val result = repo.onUserLogin(request)
result
.onSuccess {/* update UI when login success */ }
.onError { /* update UI when login error */ }
}
}
编辑:
我正在我的新应用程序中尝试这个解决方案,我发布了如果 launchSafe 方法发生错误并尝试重试请求,launcSafe() 方法将无法正常工作。所以我改变了这样的逻辑,问题就解决了。
fun CoroutineScope.launchSafe(
onError: (Throwable) -> Unit = {},
onSuccess: suspend () -> Unit
) {
launch {
try {
onSuccess()
} catch (e: Exception) {
onError(e)
}
}
}
旧答案:
关于这个话题我想了很多,想出了一个解决方案。我认为这个解决方案更清洁且易于处理异常。首先,当使用像
这样的代码时
fun getNames() = launch { }
您正在将作业实例返回给 ui 我认为这是不正确的。 Ui 不应引用作业实例。我尝试了以下解决方案,它对我很有用。但我想讨论是否会发生任何副作用。很高兴看到您的评论。
fun main() {
Presenter().getNames()
Thread.sleep(1000000)
}
class Presenter(private val repository: Repository = Repository()) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default // Can be Dispatchers.Main in Android
fun getNames() = launchSafe(::handleLoginError) {
println(repository.getNames())
}
private fun handleLoginError(throwable: Throwable) {
println(throwable)
}
fun detach() = this.cancel()
}
class Repository {
suspend fun getNames() = suspendCancellableCoroutine<List<String>> {
val timer = Timer()
it.invokeOnCancellation {
timer.cancel()
}
timer.schedule(timerTask {
it.resumeWithException(IllegalArgumentException())
//it.resume(listOf("a", "b", "c", "d"))
}, 500)
}
}
fun CoroutineScope.launchSafe(
onError: (Throwable) -> Unit = {},
onSuccess: suspend () -> Unit
) {
val handler = CoroutineExceptionHandler { _, throwable ->
onError(throwable)
}
launch(handler) {
onSuccess()
}
}
第一次使用协程。需要帮助。
这是我的流程:
Presenter 想要登录所以调用存储库接口。 Repository 实现 RepositoryInterface。 所以 Repository 调用 APIInterface。 APIInterface由APIInterfaceImpl实现。 APIInterfaceImpl最终调用了MyRetrofitInterface。
这是流程图:
Presenter -> Repository -> APIInterfaceImpl -> MyRetrofitInterface
收到登录响应后:
APIInterfaceImpl -> 存储库 -> 将数据存储在缓存中 -> 将 http 状态代码提供给 Presenter
这是我的代码:
RepositoryInterface.kt
fun onUserLogin(loginRequest: LoginRequest): LoginResponse
Repository.kt
class Repository : RepositoryInterface {
private var apiInterface: APIInterface? = null
override fun onUserLogin(loginRequest: LoginRequest): LoginResponse {
return apiInterface?.makeLoginCall(loginRequest)
}
}
APIInterface.kt
suspend fun makeLoginCall(loginRequest): LoginResponse?
APIInterfaceImpl.kt
override suspend fun makeLoginCall(loginRequest: LoginRequest): LoginResponse? {
if (isInternetPresent(context)) {
try {
val response = MyRetrofitInterface?.loginRequest(loginRequest)?.await()
return response
} catch (e: Exception) {
//How do i return a status code here
}
} else {
//How do i return no internet here
return Exception(Constants.NO_INTERNET)
}
}
MyRetrofitInterface.kt
@POST("login/....")
fun loginRequest(@Body loginRequest: LoginRequest): Deferred<LoginResponse>?
我的问题是:
- 我的方法在架构上是否正确?
- 如何在我的代码中传递 http 错误代码或没有互联网连接
- 我的解决方案还有更好的方法吗?
在本地范围内启动协程是一种很好的做法,它可以在生命周期感知 classes 中实现,例如 Presenter 或 ViewModel。您可以使用下一种方法来传递数据:
在单独的文件中创建
sealed
Result
class 及其继承者:sealed class Result<out T : Any> class Success<out T : Any>(val data: T) : Result<T>() class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
使
onUserLogin
函数可挂起并在RepositoryInterface
和Repository
中返回Result
:suspend fun onUserLogin(loginRequest: LoginRequest): Result<LoginResponse> { return apiInterface.makeLoginCall(loginRequest) }
根据以下代码更改
APIInterface
和APIInterfaceImpl
中的makeLoginCall
函数:suspend fun makeLoginCall(loginRequest: LoginRequest): Result<LoginResponse> { if (isInternetPresent()) { try { val response = MyRetrofitInterface?.loginRequest(loginRequest)?.await() return Success(response) } catch (e: Exception) { return Error(e) } } else { return Error(Exception(Constants.NO_INTERNET)) } }
为您的
Presenter
使用下一个代码:class Presenter(private val repo: RepositoryInterface, private val uiContext: CoroutineContext = Dispatchers.Main ) : CoroutineScope { // creating local scope private var job: Job = Job() // To use Dispatchers.Main (CoroutineDispatcher - runs and schedules coroutines) in Android add // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1' override val coroutineContext: CoroutineContext get() = uiContext + job fun detachView() { // cancel the job when view is detached job.cancel() } fun login() = launch { // launching a coroutine val request = LoginRequest() val result = repo.onUserLogin(request) // onUserLogin() function isn't blocking the Main Thread //use result, make UI updates when (result) { is Success<LoginResponse> -> { /* update UI when login success */ } is Error -> { /* update UI when login error */ } } } }
编辑
我们可以在 Result
class 上使用扩展函数来替换 when
表达式:
inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
if (this is Error) action(this)
return this
}
class Presenter(...) : CoroutineScope {
// ...
fun login() = launch {
val request = LoginRequest()
val result = repo.onUserLogin(request)
result
.onSuccess {/* update UI when login success */ }
.onError { /* update UI when login error */ }
}
}
编辑:
我正在我的新应用程序中尝试这个解决方案,我发布了如果 launchSafe 方法发生错误并尝试重试请求,launcSafe() 方法将无法正常工作。所以我改变了这样的逻辑,问题就解决了。
fun CoroutineScope.launchSafe(
onError: (Throwable) -> Unit = {},
onSuccess: suspend () -> Unit
) {
launch {
try {
onSuccess()
} catch (e: Exception) {
onError(e)
}
}
}
旧答案:
关于这个话题我想了很多,想出了一个解决方案。我认为这个解决方案更清洁且易于处理异常。首先,当使用像
这样的代码时fun getNames() = launch { }
您正在将作业实例返回给 ui 我认为这是不正确的。 Ui 不应引用作业实例。我尝试了以下解决方案,它对我很有用。但我想讨论是否会发生任何副作用。很高兴看到您的评论。
fun main() {
Presenter().getNames()
Thread.sleep(1000000)
}
class Presenter(private val repository: Repository = Repository()) : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default // Can be Dispatchers.Main in Android
fun getNames() = launchSafe(::handleLoginError) {
println(repository.getNames())
}
private fun handleLoginError(throwable: Throwable) {
println(throwable)
}
fun detach() = this.cancel()
}
class Repository {
suspend fun getNames() = suspendCancellableCoroutine<List<String>> {
val timer = Timer()
it.invokeOnCancellation {
timer.cancel()
}
timer.schedule(timerTask {
it.resumeWithException(IllegalArgumentException())
//it.resume(listOf("a", "b", "c", "d"))
}, 500)
}
}
fun CoroutineScope.launchSafe(
onError: (Throwable) -> Unit = {},
onSuccess: suspend () -> Unit
) {
val handler = CoroutineExceptionHandler { _, throwable ->
onError(throwable)
}
launch(handler) {
onSuccess()
}
}