Kotlin coroutines handle error and implementation
EDIT:
I am trying this solution in my new app and i released that if an error occurs in launchSafe method and try to retry request, launcSafe() method does not work correctly. So i changed the logic like this and problem is fixed.
fun CoroutineScope.launchSafe(
onError: (Throwable) -> Unit = {},
onSuccess: suspend () -> Unit
) {
launch {
try {
onSuccess()
} catch (e: Exception) {
onError(e)
}
}
}
OLD ANSWER:
I think a lot about this topic and came with a solution. I think this solution cleaner and easy to handle exceptions. First of all when use write code like
fun getNames() = launch { }
You are returning job instance to ui i think this is not correct. Ui should not have reference to job instance. I tried below solution it's working good for me. But i want to discuss if any side effect can occur. Appreciate to see your comments.
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()
}
}
It is a good practice to launch a coroutine in a local scope which can be implemented in a lifecycle aware classes, for example Presenter or ViewModel. You can use next approach to pass data:
Create
sealed
Result
class and its inheritors in separate file: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>()
Make
onUserLogin
function suspendable and returningResult
inRepositoryInterface
andRepository
:suspend fun onUserLogin(loginRequest: LoginRequest): Result<LoginResponse> { return apiInterface.makeLoginCall(loginRequest) }
Change
makeLoginCall
function inAPIInterface
andAPIInterfaceImpl
according to the following code: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)) } }
Use next code for your
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 */ } } } }
EDIT
We can use extension functions on Result
class to replace when
expression:
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 */ }
}
}