Modern Network Calls and Retrofits, Coroutines y Sealed Classes en Android | Por Humble Presence | Enero de 2022



Hoy veremos cómo realizar solicitudes de API en Android de una manera «moderna» utilizando algunas de las funciones que ofrece Kotlin con la biblioteca Retrofit2.
¿Qué estamos tratando de crear?
Para este pequeño ejercicio, intentaremos obtener una lista dada de restaurantes del servidor de Yelp término de búsqueda y Lugar
Nota: tendrá que desarrollador Yelp Obtenga la clave API que se utilizará para enviar la solicitud
¿Qué todas las dependencias necesito?
// GSON converter for serialisation
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'// Retrofit2 dependancy
implementation 'com.squareup.retrofit2:retrofit:2.9.0'// Coroutine dependency
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'// For launching suspended function in ViewModel scope
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
Agregue la línea anterior debajo de sus dependencias aplicación/construir.gradle documento
¡Empecemos!
Nota: Este blog asume que conoces Kotlin y que manejarás la capa Vista (Actividad/fragmento) tú mismo
- Actualización de configuración
Agregue a su directorio src, los siguientes archivos: Servicios/Red/RetrofitInstance.kt
const val BASE_URL = "https://api.yelp.com/v3/businesses/"
const val AUTH_HEADER = "Authorization"
const val API_KEY = "Jw0oIMgpId1" //Add Valid API Key from yelpobject RetrofitInstance {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(
OkHttpClient()
.newBuilder()
.addInterceptor { chain ->
chain.proceed(chain
.request()
.newBuilder()
.addHeader(AUTH_HEADER, "BEARER $API_KEY")
.build()
)
}
.build()
)
.addConverterFactory(GsonConverterFactory.create())
.build()fun getYelpServices(): YelpServices {
return retrofit.create(YelpServices::class.java)
}
}
El código anterior usa RmiEl generador de trofit devuelve una instancia de Retrofit que se puede usar para crear puntos finales de API desde la interfaz de servicio (que implementaremos más adelante). También agrega el encabezado de Autorización a todas las solicitudes enviadas mediante esta instancia de actualización.
2. Agregue un archivo de interfaz de servicio en – services/network/YelpServices.kt
interface YelpServices {
@GET("search")
suspend fun search(
@Query("term") term: String,
@Query("location") location: String = 'california',
@Query("limit") limit: Int = 50,
): SearchResponse
}
Las instancias de actualización se crean y los puntos finales se implementan utilizando este archivo retrofit.create(YelpServices::class.java). sobre búsqueda El método envía una solicitud «GET» a «https://api.yelp.com/v3/businesses/search» junto con el término, la ubicación y las restricciones de los parámetros de consulta.
3. Configurar un objeto de transferencia de datos (DTO) para nuestra respuesta
DTO es solo una palabra elegante para llamar a una clase de datos de Kotlin que simula el cuerpo JSON de nuestra respuesta. Cree un archivo de clase de datos: services/network/dto/SearchResponse.kt.
data class SearchResponse(
@SerializedName("businesses")
val businesses: List,@SerializedName("total")
val total: Int,
) {
data class BusinessResponse(
@SerializedName("id")
val id: String,
@SerializedName("name")
val name: String,
@SerializedName("image_url")
val imageUrl: String,
@SerializedName("is_closed")
val isClosed: Boolean,
@SerializedName("categories")
val categories: List,
@SerializedName("price")
val price: String?,
@SerializedName("rating")
val rating: Float,
@SerializedName("phone")
val phone: String,
)
data class BusinessCategoryResponse(
@SerializedName("title")
val title: String,
)
}
@SerializedName(«Clave») Ayuda a Retrofit a decidir qué clave en la respuesta JSON debe coincidir con qué propiedad de clase.
4. Asignar DTO a modelos fuera de línea que se utilizarán para la lógica empresarial
Siempre es una buena práctica separar la lógica comercial de los objetos web que recibimos. Entonces, creemos un modelo de negocios y un mapeador para mapear nuestros DTO al modelo.
Primero vamos a crear una clase de datos modelo en – services/offline/models/Business.kt
data class Business(
val id: String,
val name: String,
val imageUrl: String,
val isClosed: Boolean,
val categories: List,
val price: String,
val rating: Float,
val phone: String,
)
A continuación, cree un archivo de interfaz de Mapper en – services/offline/Mapper.kt
interface Mapper {
fun toBusinessModel(
businessResponse: BusinessResponse
): Business
}
Ahora proporcionemos una implementación de esta interfaz en – services/offline/MapperImpl.kt
object MapperImpl : Mapper {
override fun toBusinessModel(
businessResponse: SearchResponse.BusinessResponse
): Business {
return Business(
id = businessResponse.id,
name = businessResponse.name,
imageUrl = businessResponse.imageUrl,
isClosed = businessResponse.isClosed,
categories = businessResponse.categories
.map { item -> item.title },
price = businessResponse.price ?: "$",
rating = businessResponse.rating,
phone = businessResponse.phone,
)
}
}
Ahora podemos usar el mapeador anterior para convertir la respuesta DTO de la red al modelo comercial en la respuesta a la solicitud.
5. Crear contenedores de respuesta para las respuestas de la API
Puede haber muchos problemas con las operaciones de IO, como problemas simples como no proporcionar datos válidos en la solicitud, tener problemas de back-end o tener una conexión a Internet inestable. Por lo tanto, es mejor tener controladores de errores, pero más a menudo la interfaz de usuario no necesita profundizar en lo que está mal, solo necesita saber qué está mal para poder actualizar la pantalla de manera adecuada. Eso es exactamente con lo que ayudará nuestro contenedor de respuesta. Ayudará a la capa Vista a saber si nuestra solicitud tuvo éxito o falló 🙁
Cree un archivo llamado ApiResponse en – services/network/ApiResponse.kt
sealed class ApiResponse (
data: T? = null,
exception: Exception? = null
) {
data class Success (val data: T) : ApiResponse(data, null)data class Error (
val exception: Exception
) : ApiResponse(null, exception)
}
6. Ahora hagamos la solicitud real 🥳🥳
Cree un archivo llamado MainRepository en –repositories/MainRepository.kt
object MainRepository {suspend fun search(term: String): ApiResponse
} catch (e: HttpException) {> {
return try {
val response = RetrofitInstance
.getYelpServices()
.search(term = term)
val businessList = response
.businesses.map {
MapperImpl.toBusinessModel(it)
}
ApiResponse.Success(data = businessList)
//handles exception with the request
ApiResponse.Error(exception = e) } catch (e: IOException) {
//handles no internet exception
ApiResponse.Error(exception = e)
}
}
}
- Las funciones de suspensión nos ayudan a esperar a que llegue una respuesta y luego convertir el DTO en un modelo comercial para pasar a la persona que llama.
- try-catch wrap nos ayuda a detectar excepciones comunes que pueden ocurrir en las solicitudes
7. Ahora use ViewModel para recibir los datos en la capa de vista
class MainViewModel: ViewModel() {
private val _list = MutableLiveData>(emptyList())
private val list: LiveData> = _listfun search(term: String) {
viewModelScope.launch(Dispatchers.IO) {
val response = MainRepository.search(term)
if (response is ApiResponse.Success) {
_list.value = response.data
}
if (response is ApiResponse.Error) {
// handle error
}
}
}
}
Ahora puede ver la lista en la vista y actualizar los datos.
¡Eso es!
Ha completado con éxito todas las disposiciones requeridas para realizar una solicitud de API. Este enfoque asegura las siguientes ventajas:
- Las funciones de suspensión ayudan a evitar el infierno de devolución de llamada y escriben un código más limpio
- try-catch lo ayuda a manejar las excepciones Api/IO con gracia
- Clase contenedora ApiResponse para ayudarlo a manejar estados de éxito/error en la capa de vista
- Un DTO independiente garantiza que su lógica comercial no dependa de la estructura de la respuesta, y cualquier actualización futura de la estructura de respuesta de la API no requerirá que refactorice gran parte de su código.
Si te ha resultado útil este artículo, compártelo y deja un me gusta. ¡Rápido!