【问题标题】:Using coroutines in a right way以正确的方式使用协程
【发布时间】:2019-06-07 23:25:41
【问题描述】:

我是第一次实现协程。我正在为一个简单的登录应用程序遵循 MVP 模式。这是我的代码流程-

点击的登录按钮会按照这个方向-

LoginFragment -> LoginPresenter -> Repository -> APIRepository -> RetrofitInterface

登录响应将遵循这个方向 -

RetrofitInterface -> APIRepository -> Repository -> LoginPresenter -> LoginFragment

这里是代码-

RetrofitInterface.kt

@POST("login")
    fun loginAPI(@Body loginRequest: LoginRequest): Deferred<LoginResponse>?

这是我的 Result.kt

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>()
}

APIRepository.kt

override suspend fun loginAPICall(loginRequest: LoginRequest) : Result<LoginResponse>? {
        try {
            val loginResponse = apiInterface?.loginAPI(loginRequest)?.await()
            return Result.Success<LoginResponse>(loginResponse!!)
        } catch (e : HttpException) {
            return Result.Error(e)
        } catch (e : Throwable) {
            return Result.Error(e)
        }
    }

Repository.kt

override suspend fun loginUser(loginRequest: LoginRequest): Result<LoginResponse> {
        if (isInternetPresent(context)) {
            val result = apiRepositoryInterface?.loginAPICall(loginRequest)
            if (result is Result.Success<LoginResponse>) {
                val loginData = result.data
                cache?.storeData(loginData)
            }
            return result!!
        } else {
            return Result.Error(Exception())
        }
    }

我现在如何在我的 Presenter 中启动协程?我需要在后台线程上执行此 API 调用并在 UI 线程上发布结果吗?

【问题讨论】:

    标签: java android kotlin kotlin-coroutines mvp


    【解决方案1】:

    您需要使用本地范围在 Presenter 中启动协程并注入 CoroutineContext 以便能够更改它,例如在单元测试中:

    class Presenter(private val repo: Repository,
                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 dependency: 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(request: LoginRequest) = launch { // launching a coroutine
            val result = repo.loginUser(request) // calling 'loginUser' function will not block the Main Thread, it suspends the coroutine
    
            //use result, update UI
            when (result) {
                is Success<LoginResponse> -> { /* update UI when login success */ } 
                is Error -> { /* update UI when login error */ }
            }
        }
    }
    

    【讨论】:

    • 在我的演示者登录乐趣中,我执行了 repository.loginUser(request)。但是它在 loginUser(request) 上显示错误,说必须从另一个挂起函数或协程调用挂起函数,尽管在我的存储库中 loginUser(request) 是一个挂起函数。
    • 您是否将 launch builder 添加到 Presenter 中的函数中? fun login(request: LoginRequest) = launch {...}
    • 奇怪,它不应该显示错误,因为您在协程中调用该函数,使用launch
    • 是的,我也不懂
    • 你绝对应该使用 SupervisorJob 而不是 job。否则,如果您启动另一个单独的作业,即 getUser 并且它失败了,它也可能导致您的登录失败。
    【解决方案2】:

    你可以这样使用协程

      private var parentJob = Job()
    
        private val coroutineContext: CoroutineContext
            get() = parentJob + Dispatchers.Main
    
        private val scope = CoroutineScope(coroutineContext)
    
    
    
     scope.launch(Dispatchers.IO) {
    
         // your api call 
    
     }
    

    你可以调用parentJob.cancel()取消作业或者在ViewModel的onClear中调用

    【讨论】:

    • 为什么选择 dispatchers.io?根据文档,挂起已经是一个非阻塞线程函数,我需要在 Ui 上观察结果。所以应该是dispatchers.main吧?如果我错了,请纠正我。这对我来说也是新的
    猜你喜欢
    • 2019-01-27
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多