Podría haber muchas formas de manejar las respuestas de la API que provienen de los servidores en Android, pero ¿utiliza una buena forma de manejarlas? En este artículo, veremos las respuestas de la API gestionadas con la ayuda de la clase sellada de Kotlin mientras usamos la biblioteca Retrofit para las llamadas a la API. Este enfoque de clase sellada encaja perfectamente en este caso: si el usuario espera datos en la interfaz de usuario, entonces debemos enviar los errores a nuestra interfaz de usuario para notificar al usuario y asegurarnos de que el usuario no solo vea una pantalla en blanco. o experimentar una interfaz de usuario inesperada.
Hacer clase sellada
Nota : estamos siguiendo la arquitectura MVVM y usando Retrofit con Kotlin Coroutines para llamadas api en segundo plano.
Simplemente cree una clase sellada genérica llamada Recurso en un archivo separado en su paquete de datos o en el paquete utils/others. Lo llamamos Recurso y lo creamos genérico porque usaremos esta clase para envolver nuestros diferentes tipos de respuestas API. Básicamente, necesitamos actualizar nuestra interfaz de usuario en estos tres eventos, es decir, éxito, error, carga. Por lo tanto, los hemos creado como clases secundarias de recursos para representar diferentes estados de la interfaz de usuario. Ahora, en caso de éxito, obtendremos datos, así que los envolveremos con Resource. Éxito y, en caso de error, envolveremos el mensaje de error con Resource. Error y en caso de carga, simplemente devolveremos el objeto Resource.Loading (también puede modificarlo para envolver sus datos de carga según sus necesidades).
Kotlin
sealed class Resource<T>( val data: T? = null, val message: String? = null ) { // We'll wrap our data in this 'Success' // class in case of success response from api class Success<T>(data: T) : Resource<T>(data = data) // We'll pass error message wrapped in this 'Error' // class to the UI in case of failure response class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage) // We'll just pass object of this Loading // class, just before making an api call class Loading<T> : Resource<T>() }
También echemos un vistazo a nuestra interfaz ApiService (ver justo debajo), aquí, en cada función de llamada API suspendida, estamos envolviendo nuestra respuesta de llamada API con la clase de respuesta de Retrofit porque nos brinda información adicional sobre las llamadas API. Por ejemplo: si las llamadas tienen éxito o no, código de error, etc. Llamaremos a estas funciones suspendidas desde nuestros repositorios… lo veremos dentro de un rato. No se confunda con nuestra clase de recursos personalizada y la clase de respuesta de Retrofit. La clase de recursos solo representa diferentes estados de la interfaz de usuario, mientras que la clase de respuesta de Retrofit nos brinda información adicional sobre las llamadas a la API.
Kotlin
interface ExampleApiService { @GET("example/popular_articles") suspend fun fetchPopularArticles(): Response<PopularArticlesResponse> // We have wrapped our api response in // Retrofit's Response class. So that we can have // some extra information about api call response // like weather it succeed or not, etc @GET("example/new_articles") suspend fun fetchNewArticles(): Response<NewArticlesResponse> }
Ahora pasemos a la parte principal del manejo de errores/éxitos de la API.
Debe tener diferentes repositorios en su proyecto de acuerdo con sus necesidades y todas sus llamadas a la API deben realizarse a través de estos repositorios. Necesitamos gestionar los errores de todas y cada una de las llamadas a la API. Por lo tanto, debemos envolver cada llamada API dentro del bloque try-catch . Pero espera… escribir un bloque try-catch para cada llamada a la API no es una buena idea. Entonces, creemos un BaseRepository y escribamos una función de suspensión común allí llamada safeApiCall (cualquier nombre que desee) que será responsable de manejar las respuestas de API de todas y cada una de las llamadas de API.
Kotlin
abstract class BaseRepo() { // we'll use this function in all // repos to handle api errors. suspend fun <T> safeApiCall(apiToBeCalled: suspend () -> Response<T>): Resource<T> { // Returning api response // wrapped in Resource class return withContext(Dispatchers.IO) { try { // Here we are calling api lambda // function that will return response // wrapped in Retrofit's Response class val response: Response<T> = apiToBeCalled() if (response.isSuccessful) { // In case of success response we // are returning Resource.Success object // by passing our data in it. Resource.Success(data = response.body()!!) } else { // parsing api's own custom json error // response in ExampleErrorResponse pojo val errorResponse: ExampleErrorResponse? = convertErrorBody(response.errorBody()) // Simply returning api's own failure message Resource.Error(errorMessage = errorResponse?.failureMessage ?: "Something went wrong") } } catch (e: HttpException) { // Returning HttpException's message // wrapped in Resource.Error Resource.Error(errorMessage = e.message ?: "Something went wrong") } catch (e: IOException) { // Returning no internet message // wrapped in Resource.Error Resource.Error("Please check your network connection") } catch (e: Exception) { // Returning 'Something went wrong' in case // of unknown error wrapped in Resource.Error Resource.Error(errorMessage = "Something went wrong") } } } // If you don't wanna handle api's own // custom error response then ignore this function private fun convertErrorBody(errorBody: ResponseBody?): ExampleErrorResponse? { return try { errorBody?.source()?.let { val moshiAdapter = Moshi.Builder().build().adapter(ExampleErrorResponse::class.java) moshiAdapter.fromJson(it) } } catch (exception: Exception) { null } } }
safeApiCall tiene una función lambda suspendida llamada ‘api’ que devuelve datos envueltos en Resource para que safeApiCall pueda recibir todas nuestras funciones de llamada api. Como safeApiCall es una función de suspensión, estamos usando el alcance de la rutina withContext con Dispatchers.IO, de esta manera todas las llamadas de red se ejecutarán en el subproceso de fondo de entrada/salida. Cada vez que una llamada de red falla, arroja una excepción, por lo que debemos llamar a nuestra función lambda ‘apiToBeCalled’ dentro del bloque try-catch y aquí, si la llamada api tiene éxito, envolveremos nuestros datos de respuesta exitosa en Resource. Objeto de éxito y lo devolveremos a través de safeApiCall y si la llamada api falla, detectaremos esa excepción en particular en uno de los siguientes bloques de captura:
- Cuando algo salió mal en el servidor al procesar una solicitud HTTP, arrojará una excepción HTTP.
- Cuando el usuario no tenga una conexión válida a Internet/wifi, lanzará una excepción Java IO.
- Si se produce alguna otra excepción, simplemente aparecerá en el bloque de excepción general (coloque ese bloque en último lugar). También puede agregar otro bloque de captura de excepciones de acuerdo con su caso específico antes del último bloque de captura de excepción general.
Ahora, en los bloques catch, podemos simplemente enviar nuestros propios mensajes de error personalizados o mensajes de error obtenidos de excepciones en el objeto Resource.Error.
Nota : En caso de que la llamada de red no falle y no arroje una excepción si está enviando su propia respuesta json de error personalizada (a veces ocurre en caso de falla de autenticación/token caduca/clave api no válida/etc) entonces ganamos En su lugar, no obtendrá una excepción. La clase de respuesta de Retrofit nos ayudará a determinar devolviendo verdadero/falso en la respuesta. En el código anterior también estamos manejando eso dentro del bloque de prueba, si devuelve falso, entonces dentro del caso de lo contrario analizaremos la propia respuesta json de error personalizada de la API (ver el ejemplo a continuación) en una clase pojo creada por nosotros, es decir, ExampleErrorResponse. Para analizar esta respuesta json a nuestro pojo ExampleErrorResponse, estamos usando la biblioteca Moshien nuestra función convertErrorBody que devolverá el objeto ExampleErrorResponse y luego podemos obtener la respuesta de error personalizada de la API.
{ “status”: ”UnSuccessful”, “failure_message” : ”Invalid api key or api key expires” }
Entonces, en última instancia, nuestra función safeApiCall devolverá nuestra respuesta envuelta en la clase Resource. podría ser Éxito o Error. Si has entendido hasta este punto, entonces te felicito. Has ganado la mayor parte de la batalla.
Ahora cambiemos la forma de llamar a Apis en sus repositorios actuales
Ampliaremos todos nuestros repositorios desde BaseRepo para que podamos acceder a la función safeApiCall allí. Y dentro de las funciones de nuestro repositorio como getPopularArticles, pasaremos la función de nuestro ApiService (por ejemplo: apiService.fetchPopularArticles() ) como un argumento lambda en safeApiCall y luego safeApiCall simplemente manejará su parte de éxito/error y finalmente devolverá Resource<T > eso irá hasta la parte superior de la interfaz de usuario a través de ViewModel.
Nota : Recuerde que cuando llamemos a la función getPopularArticles en el modelo de vista (vea el ejemplo a continuación), primero publicaremos el objeto Resource.Loading en datos en vivo mutables de modo que nuestro fragmento/actividad pueda observar este estado de carga y pueda actualizar la interfaz de usuario para mostrar la carga estado y luego, cuando tengamos lista nuestra respuesta API, se publicará nuevamente en datos en vivo mutables para que la interfaz de usuario pueda obtener esta respuesta (éxito/error) actualizada nuevamente y pueda mostrar datos en la interfaz de usuario o actualizarse en consecuencia.
Kotlin
class ExampleRepo(private val apiService: ExampleApiService) : BaseRepo() { suspend fun getPopularArticles() : Resource<PopularArticlesResponse> { // Passing 'api.fetchPopularArticles()' function // as an argument in safeApiCall function return safeApiCall { apiService.fetchPopularArticles() } } suspend fun getPublishedArticles() : Resource<NewlyPublishedArticlesResponse> = safeApiCall { apiService.fetchPublishedArticles() } }
Ver modelo
Kotlin
class ExampleViewModel (private val exampleRepo: ExampleRepo) { private val _popularArticles = MutableLiveData<Resource<PopularArticlesResponse>>() val popularArticles: LiveData<Resource<PopularArticlesResponse>> = _popularArticles init { // Calling this function in init{} // block so that it'll automatically // get called on initialization of viewmodel getPopularArticles() } private fun getPopularArticles() = viewModelScope.launch { // Firstly we are posting // Loading state in mutableLiveData _popularArticles.postValue(Resource.Loading()) // Posting success response // as it becomes ready _popularArticles.postValue(exampleRepo.getPopularArticles()) } }
Eso es todo, ahora podemos observar todos los estados, es decir, Éxito, Error, Cargando en nuestra interfaz de usuario fácilmente. Eso fue sobre el manejo de errores de API o el manejo de respuestas de API de una mejor manera.
Publicación traducida automáticamente
Artículo escrito por haiderfaizan732 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA