certmundo.
es‑mx

6 min de lectura

¿Cómo consumir una API REST con Retrofit y Coroutines en Kotlin?

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:

  1. Data class — representa el JSON que devuelve la API.
  2. Interface — define los endpoints (rutas) de la API.
  3. 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.

Puntos clave

  • Retrofit necesita tres piezas: una **data class** que mapea el JSON, una **interface** con los endpoints anotados (`@GET`, `@POST`, etc.), y un objeto singleton que construye la instancia con `Retrofit.Builder()`.
  • Declara los endpoints como funciones `suspend` en la interface para poder llamarlos desde Coroutines sin bloquear el hilo principal.
  • Usa `viewModelScope.launch` para lanzar Coroutines en el ViewModel; se cancelan automáticamente cuando el ViewModel se destruye y evitan fugas de memoria.
  • Envuelve siempre la llamada a la API en un bloque `try/catch` para manejar errores de red, y usa `Response<T>` cuando necesites inspeccionar el código HTTP de la respuesta.
  • La `BASE_URL` en `Retrofit.Builder()` debe terminar con `/`, y el permiso `INTERNET` en `AndroidManifest.xml` es obligatorio para que cualquier petición funcione.

Comparte esta lección:

¿Cómo consumir una API REST con Retrofit y Coroutines en Kotlin? | Kotlin para Android: Curso Práctico | Certmundo