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