Kotlin — Suspend function + Retrofit 1.9 + Either

Cuando quieres mandar a volar a tus callbacks

Armando Picón
3 min readJun 10, 2020

--

Hace poco tuve que pasar algunas funciones que estaban implementadas con el uso de callbacks y queríamos usar un tipo de retorno en ellas.

Imaginen el siguiente código consumiendo un cliente de Retrofix 1.9

fun reposForUser(parameter: String, onSuccess: (List<GitHubRepo>) -> Unit, onFailure: (RetrofitError) -> Unit) {
client.reposForUser(parameter, object : Callback<List<GitHubRepo>> {
override fun success(repos: List<GitHubRepo>, response: Response?) {
onSuccess.invoke(repos)
}

override fun failure(error: RetrofitError) {
onFailure.invoke(error)
}
})
}

¿Qué pasaría si deseara convertir esta función en una función suspendida para consumir?

  • Marcar la función como suspend
  • Luego, deshacernos de los callbacks porque no tendrían sentido en el contexto de una suspend function.
  • Cambiar, el tipo de retorno

En este caso quizá el único inconveniente es el callback que por defecto genera Retrofix 1.9, pero aún ello lo podemos alinear con las corutinas de la siguiente manera:

suspend fun reposForUser(parameter: String): Either<RetrofitError, List<GitHubRepo>> =
suspendCoroutine { continuation ->
client.reposForUser(parameter, object : Callback<List<GitHubRepo>> {
override fun success(repos: List<GitHubRepo>, response: Response?) {
continuation.resume(Either.Right(repos))
}

override fun failure(error: RetrofitError) {
continuation.resume(Either.Left(error))
}
})
}
}

Esta idea proviene de una respuesta dada por Roman Elizarov en una discusión de .

La definición de la función suspendCoroutine{} la podemos encontrar ; sin embargo, en resumen nos permite obtener la instancia del objeto que se emplea para retomar la ejecución del código durante una función suspendida. Este objeto lo usamos para indicar que una vez que retorne la respuesta del servicio continúe con la ejecución retornando el objecto que se le está pasando como parámetro (y que coincide con el tipo de retorno que hemos declarado).

Aquí es donde aparece el tipo Either

Alguna persona podría reclamar el por qué no usamos la funcion continuation.resumeWith; sin embargo, no lo empleamos porque para el caso de error demanda el retorno de un Throwable (una excepción), el asunto es que casos como estos donde los resultados son medianamente esperados no merecen ser tratados como excepción y por eso recurrimos a un objeto de tipo Either.

Pero ¿qué rayos es?

Si nunca has oído hablar de este tipo no te culpo. Es muy usado en programación funcional. Es una y esta representa un único valor de dos tipos posibles.

Una instancia de Either cuenta con dos elementos Left o Right y, por convención, Left se usa para manejar los casos de error y Right se usa para los casos de éxito.

¿Cómo se implementa?

Se implementa de la siguiente manera:

sealed class Either<out ErrorType, out ResultType> {
class Left<ErrorType>(val value: ErrorType) : Either<ErrorType, Nothing>()
class Right<ResultType>(val value: ResultType) : Either<Nothing, ResultType>()
}

Al solo contar con dos combinaciones posibles Fallo o Éxito; se parte definiendo una clase sellada y se definen los dos posibles tipos.

Aplicándolo a la nueva implementación, estamos definiendo Either<RetrofitError, List<GitHubRepo>>; es decir que nuestro valor de retorno en caso que se produzca un error será del tipo RetrofitError y para el caso de éxito nuestro valor de retorno será del tipo List<GitHubRepo>.

Conclusión

Claramente lo ideal sería usar Retrofit 2.X que ya cuenta con soporte a corutinas; pero si no y aún estás algo desfasado (por no decir mucho) esta podría ser una buena alternativa para refactorizar el consumo de tus servicios y emplear corutinas.

Si consideras que hay alguna mejor estrategia para lidiar con este escenario te invito a que me lo hagas saber a través de los comentarios. Siempre es genial ver otras soluciones.

--

--

Armando Picón
Armando Picón

Written by Armando Picón

lifelong learner • 👨🏽‍💻 Android eng (ex: @uber ) • 🇵🇪 @ 🇨🇱 • @gdgopen • content creator @devpicon | @primosauda

No responses yet