Retrofit es una librería de Square que convierte una API REST en una interfaz de Kotlin, y combinada con Coroutines permite hacer peticiones HTTP de forma asíncrona sin bloquear el hilo principal.
La mayoría de las apps modernas — desde Mercado Libre hasta Liverpool — obtienen sus datos de servidores remotos. Esta lección te muestra cómo conectar tu app Android a una API REST de manera segura y eficiente.
Dependencias necesarias
Antes de escribir código, agrega las dependencias en tu archivo build.gradle (Module: app):
dependencies {
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
// ViewModel + LiveData (para el siguiente paso)
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
}
También debes agregar el permiso de internet en AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Sin este permiso, todas tus peticiones fallarán en silencio.
Las tres piezas de Retrofit
Retrofit se organiza en tres componentes principales:
- Data class — representa el JSON que devuelve la API.
- Interface — define los endpoints (rutas) de la API.
- Instancia de Retrofit — configura la URL base y el convertidor de JSON.
1. Data class
Imagina que consumes una API de productos de una tienda en línea similar a Liverpool. El JSON de respuesta luce así:
{
"id": 101,
"nombre": "Cafetera espresso",
"precio": 1299.0,
"disponible": true
}
Crea una data class que mapee ese JSON:
data class Producto(
val id: Int,
val nombre: String,
val precio: Double,
val disponible: Boolean
)
Gson (el convertidor) empareja automáticamente los campos del JSON con las propiedades de la data class por nombre.
2. Interface de la API
Define los endpoints como funciones suspend. La palabra clave suspend le indica a Kotlin que esta función puede pausarse sin bloquear el hilo principal:
import retrofit2.http.GET
import retrofit2.http.Path
interface ProductoApiService {
@GET("productos")
suspend fun obtenerProductos(): List<Producto>
@GET("productos/{id}")
suspend fun obtenerProductoPorId(
@Path("id") id: Int
): Producto
}
@GET indica que es una petición GET. El valor entre comillas es la ruta relativa a la URL base.
3. Instancia de Retrofit
Crea un objeto singleton para reutilizar la misma instancia en toda la app:
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private const val BASE_URL = "https://api.mitienda.com.mx/v1/"
val apiService: ProductoApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ProductoApiService::class.java)
}
}
by lazy garantiza que Retrofit se construya solo una vez, la primera vez que lo uses.
Llamar a la API con Coroutines
Las funciones suspend solo pueden llamarse desde una Coroutine o desde otra función suspend. En Android, usa viewModelScope o lifecycleScope para lanzar Coroutines de forma segura.
Ejemplo en un ViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import androidx.lifecycle.MutableLiveData
class ProductoViewModel : ViewModel() {
val productos = MutableLiveData<List<Producto>>()
val error = MutableLiveData<String>()
fun cargarProductos() {
viewModelScope.launch {
try {
val lista = RetrofitClient.apiService.obtenerProductos()
productos.value = lista
} catch (e: Exception) {
error.value = "No se pudo conectar: ${e.message}"
}
}
}
}
viewModelScope.launch abre una Coroutine que se cancela automáticamente cuando el ViewModel se destruye. Esto evita fugas de memoria.
Observar los datos en el Fragment o Activity
viewModel.productos.observe(viewLifecycleOwner) { lista ->
adaptador.actualizarLista(lista)
}
viewModel.error.observe(viewLifecycleOwner) { mensaje ->
Toast.makeText(requireContext(), mensaje, Toast.LENGTH_SHORT).show()
}
viewModel.cargarProductos()
El Fragment solo observa. Toda la lógica de red vive en el ViewModel.
Ejemplo completo: precios de productos FEMSA
Supón que construyes una app interna para distribuidores de FEMSA. La API devuelve una lista de bebidas con su precio en pesos:
data class Bebida(
val sku: String,
val descripcion: String,
val precioUnitario: Double
)
interface FEMSAApiService {
@GET("catalogo/bebidas")
suspend fun obtenerBebidas(): List<Bebida>
}
En el ViewModel, después de cargar los datos, puedes calcular un resumen:
fun mostrarResumen(bebidas: List<Bebida>) {
val total = bebidas.sumOf { it.precioUnitario }
val promedio = total / bebidas.size
println("Total catálogo: $${String.format("%,.0f", total)}")
println("Precio promedio: $${String.format("%,.0f", promedio)}")
}
Si la API devuelve 5 bebidas con precios de $18,500, $12,300, $9,800, $15,000 y $22,400, la salida sería:
Total catálogo: $78,000
Precio promedio: $15,600
Manejo de errores HTTP
No todos los errores son excepciones de red. Un servidor puede responder con código 404 (no encontrado) o 500 (error interno). Usa Response<T> para capturar estos casos:
import retrofit2.Response
import retrofit2.http.GET
interface ProductoApiService {
@GET("productos")
suspend fun obtenerProductos(): Response<List<Producto>>
}
val respuesta = RetrofitClient.apiService.obtenerProductos()
if (respuesta.isSuccessful) {
val lista = respuesta.body() ?: emptyList()
productos.value = lista
} else {
error.value = "Error del servidor: ${respuesta.code()}"
}
isSuccessful es true para códigos 200–299. Cualquier otro código pasa al bloque else.
Errores comunes
1. Llamar a una función suspend desde el hilo principal directamente.
Nunca llames funciones suspend fuera de una Coroutine. Siempre usa viewModelScope.launch o lifecycleScope.launch.
2. Olvidar el permiso de internet.
El permiso INTERNET en el AndroidManifest.xml es obligatorio. Sin él, Retrofit lanza una excepción UnknownHostException que parece un error de red pero en realidad es un error de configuración.
3. Usar Dispatchers.Main para la petición de red.
Retrofit con Coroutines cambia automáticamente al hilo de IO. No necesitas especificar withContext(Dispatchers.IO) para las llamadas de Retrofit. Si lo haces manualmente en el hilo equivocado, obtendrás una NetworkOnMainThreadException.
4. No manejar excepciones con try/catch.
Si el dispositivo no tiene internet o el servidor no responde, Retrofit lanza una excepción. Sin try/catch, la app se cierra inesperadamente. Siempre envuelve la llamada en un bloque try/catch.
5. Confundir la URL base con el endpoint.
La BASE_URL debe terminar con /. Si escribes https://api.mitienda.com.mx/v1 (sin la diagonal al final), Retrofit puede ignorar parte de la ruta del endpoint y producir un error 404.
Tabla de referencia: anotaciones de Retrofit
| Anotación | Uso | Ejemplo |
|---|---|---|
@GET |
Petición GET | @GET("productos") |
@POST |
Petición POST | @POST("pedidos") |
@PUT |
Actualizar recurso | @PUT("productos/{id}") |
@DELETE |
Eliminar recurso | @DELETE("productos/{id}") |
@Path |
Variable en la URL | @Path("id") id: Int |
@Query |
Parámetro de búsqueda | @Query("categoria") cat: String |
@Body |
Cuerpo del request | @Body producto: Producto |
Resumen
Retrofit y Coroutines son el estándar de la industria para consumir APIs REST en Android. Retrofit traduce los endpoints a funciones de Kotlin. Las Coroutines ejecutan esas funciones de forma asíncrona sin complicar el código. Juntos, simplifican drásticamente la lógica de red en cualquier app profesional.