Usando corrutinas por primera vez. Necesitas ayuda.
Aquí está mi flujo:
El presentador quiere iniciar sesión, por lo que llama a la interfaz del repositorio. El repositorio implementa RepositoryInterface. Entonces Repository llama APIInterface. APIInterface está implementado por APIInterfaceImpl. APIInterfaceImpl finalmente llama a MyRetrofitInterface.
Aquí está el flujo esquemáticamente:
Presentador -> Repositorio -> APIInterfaceImpl -> MyRetrofitInterface
Una vez que recibo la respuesta de inicio de sesión:
APIInterfaceImpl -> Repositorio -> Almacena los datos en caché -> Da el código de estado http al presentador
Aquí está mi código:
RepositoryInterface.kt
fun onUserLogin(loginRequest: LoginRequest): LoginResponseRepositorio.kt
class Repository : RepositoryInterface { private var apiInterface: APIInterface? = null override fun onUserLogin(loginRequest: LoginRequest): LoginResponse { return apiInterface?.makeLoginCall(loginRequest) } }APIInterface.kt
suspend fun makeLoginCall(loginRequest): LoginResponse?APIInterfaceImpl.kt
override suspend fun makeLoginCall(loginRequest: LoginRequest): LoginResponse? { if (isInternetPresent(context)) { try { val response = MyRetrofitInterface?.loginRequest(loginRequest)?.await() return response } catch (e: Exception) { //How do i return a status code here } } else { //How do i return no internet here return Exception(Constants.NO_INTERNET) } }MyRetrofitInterface.kt
@POST("login/....") fun loginRequest(@Body loginRequest: LoginRequest): Deferred<LoginResponse>?Mis preguntas son:
Es una buena práctica lanzar una rutina en un ámbito local que se pueda implementar en clases conscientes del ciclo de vida, por ejemplo, Presenter o ViewModel . Puede usar el siguiente enfoque para pasar datos:
Cree la clase de Result sealed y sus herederos en un archivo separado:
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>() Haga que la función onUserLogin suspendible y devuelva el Result en RepositoryInterface y Repository :
suspend fun onUserLogin(loginRequest: LoginRequest): Result<LoginResponse> { return apiInterface.makeLoginCall(loginRequest) } Cambie la función makeLoginCall en APIInterface y APIInterfaceImpl de acuerdo con el siguiente código:
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 el siguiente código para su 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 */ } } } }EDITAR
Podemos usar funciones de extensión en la clase Result para reemplazar la expresión when :
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 */ } } }EDITAR:
Estoy probando esta solución en mi nueva aplicación y lancé que si ocurre un error en el método launchSafe y trato de volver a intentar la solicitud, el método launcSafe() no funciona correctamente. Así que cambié la lógica de esta manera y el problema está solucionado.
fun CoroutineScope.launchSafe( onError: (Throwable) -> Unit = {}, onSuccess: suspend () -> Unit ) { launch { try { onSuccess() } catch (e: Exception) { onError(e) } } }RESPUESTA ANTIGUA:
Pienso mucho sobre este tema y encontré una solución. Creo que esta solución es más limpia y fácil de manejar excepciones. En primer lugar, cuando use código de escritura como
fun getNames() = launch { }Está devolviendo la instancia de trabajo a la interfaz de usuario. Creo que esto no es correcto. Ui no debe tener referencia a la instancia de trabajo. Intenté la siguiente solución, funciona bien para mí. Pero quiero discutir si puede ocurrir algún efecto secundario. Agradezco ver sus comentarios.
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() } }