Java – Use coroutines correctly

Use coroutines correctly… here is a solution to the problem.

Use coroutines correctly

I implemented coroutines for the first time. I’m following the MVP pattern for a simple login app. This is my code flow-

The clicked login button will follow this direction-

LoginFragment ->

LoginPresenter -> Repository -> APIRepository -> RetrofitInterface

The login response will follow this direction-

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

Here is the code-

RetrofitInterface.kt

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

Here is my 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())
        }
    }

How do I start a coroutine in my Presenter now? Do I need to execute this API call on a background thread and post the results on the UI thread?

Solution

You need to start the coroutine in Presenter with local scope and inject (inject) CoroutineContext to change it, such as in unit tests:

class Presenter(
    private val repo: Repository,
    private val uiContext: CoroutineContext = Dispatchers.Main
) : CoroutineScope { // creating local scope

private var job: Job = Job() // or SupervisorJob() - children of a supervisor job can fail independently of each other

 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 */ }
        }
    }
}

Related Problems and Solutions