Kotlin coroutines handle error and implementation

后端 未结 2 965
太阳男子
太阳男子 2020-12-14 23:44

Using coroutines for the first time. Need help.

Here is my flow:

Presenter wants to login so calls Repository Interface. Repository implements RepositoryInte

相关标签:
2条回答
  • 2020-12-15 00:05

    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:

    1. 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>()
      
    2. Make onUserLogin function suspendable and returning Result in RepositoryInterface and Repository:

      suspend fun onUserLogin(loginRequest: LoginRequest): Result<LoginResponse> {
          return apiInterface.makeLoginCall(loginRequest)
      }
      
    3. Change makeLoginCall function in APIInterface and APIInterfaceImpl 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))
          }
      }
      
    4. 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 */ }
        }
    }
    
    0 讨论(0)
  • 2020-12-15 00:12

    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()
        }
    }
    
    0 讨论(0)
提交回复
热议问题