Tema 3.4 - Retrofit: red, DTOs y errores¶
La app ya guarda en el móvil. Ahora tiene que hablar con un servidor: pedir datos por HTTPS, convertirlos de JSON a objetos y comportarse bien cuando la red falla.
1. Las tres piezas (y quién hace qué)¶
| Pieza | Qué hace |
|---|---|
| DTO | Clase que representa exactamente lo que devuelve el servidor |
| Interfaz del API | Declara las llamadas (@GET, @POST…) sin implementarlas |
| Retrofit | Genera la implementación, hace la petición y convierte el JSON |
Retrofit no hace magia: por debajo usa OkHttp para la conexión. Tú solo declaras la interfaz y él genera el código que lanza la petición y parsea la respuesta.
2. El DTO: lo que devuelve el servidor¶
@Serializable // genera el parser a partir de la clase data class UsuarioRemoto( // lo que devuelve el servidor val id: Int, // campo id del JSON val login: String, // campo login del JSON val html_url: String // campo html_url del JSON )
El DTO copia el JSON del servidor, no tu modelo de negocio. Si el servidor llama login, tu DTO tiene login; la conversión a tu modelo se hace después.
3. La interfaz: declarar la llamada¶
interface AgendaApi { // contrato del servidor
@GET("users") // GET https://api.github.com/users
suspend fun usuarios(): List
suspend es obligatorio: la petición se hace en segundo plano y la función se reanuda cuando llega la respuesta, sin bloquear la interfaz.
4. El cliente: Retrofit + JSON¶
object RetrofitCliente { // una sola instancia del cliente private val json = Json { ignoreUnknownKeys = true } // si el servidor añade campos, no revienta private val retrofit = Retrofit.Builder() .baseUrl("https://api.github.com/") // URL base (termina en /) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() val api: AgendaApi = retrofit.create(AgendaApi::class.java) // implementación generada }
Dos detalles que cuestan puntos en el examen:
baseUrltermina en/y la ruta del@GETno empieza por/. Si no, Retrofit monta una URL distinta de la que crees.ignoreUnknownKeys = true: si el servidor añade un campo nuevo, tu app no revienta. Sin eso, cualquier cambio del backend rompe el parseo.
Json {{ ignoreUnknownKeys = true }} es la diferencia entre aguantar un cambio del servidor y crashear en producción.
Permisos: sin esto no hay red¶
Sin esa línea, cualquier petición falla con EPERM (Operation not permitted) en getaddrinfo — un error que parece de red y en realidad es de permisos.
5. La pantalla: tres estados, no uno¶
Una pantalla que llama a un servidor tiene tres estados: cargando, error y datos. Si solo pintas el bueno, la app parece rota cuando la red falla.
fun EscenaApi() { // 3.4: pedir datos a un servidor
val alcance = rememberCoroutineScope()
var usuarios by remember { mutableStateOf<List
Un error de red no es una excepción que se ignora: es un estado más de la pantalla. Se muestra, se explica y se permite reintentar.
6. Los fallos reales (los que me pasaron a mí)¶
| Síntoma | Causa | Solución |
|---|---|---|
EPERM en getaddrinfo |
falta el permiso INTERNET | añadirlo al Manifest y recompilar |
CertPathValidatorException: Trust anchor not found |
el certificado del dominio no encadena con una CA del móvil | usar un dominio con cadena válida o instalar la CA (solo en desarrollo) |
HTTP 404 |
la ruta no existe | revisar baseUrl + @GET |
HTTP 401/403 |
falta token o permisos | cabecera Authorization |
SocketTimeoutException |
el servidor no responde | timeouts y reintento |
| La app se cierra al pedir | excepción no capturada | try/catch/finally alrededor de la llamada |
finally {{ cargando = false }} no es opcional: si la petición falla y no lo pones, la rueda de carga se queda girando para siempre y el usuario cree que la app está colgada.
7. Errores típicos de examen¶
| Error | Por qué está mal |
|---|---|
| Llamar a Retrofit en el hilo principal | bloquea la UI; suspend lo evita |
| Un DTO con nombres inventados | el parseo falla en ejecución |
| No capturar excepciones | la app se cierra con cualquier fallo de red |
baseUrl sin / final |
la URL se monta mal |
| Devolver la lista sin estado de error | el usuario no sabe qué ha pasado |
8. Resumen¶
- DTO = forma del JSON; interfaz = llamadas; Retrofit = implementación + parseo.
- Las llamadas son
suspendy se lanzan desde una corrutina. ignoreUnknownKeyste protege de cambios del servidor.- Permiso INTERNET obligatorio.
- Una pantalla con red tiene tres estados: cargando, error, datos.
Lo que tienes que llevarte de este apartado¶
- Sin el permiso
INTERNETen elAndroidManifest.xmlla petición muere conEPERMantes de salir del teléfono: parece red y es declaración. baseUrltermina en/y la ruta del@GETno empieza por/: si no respetas esa convención, la URL sale mal y el error es un 404 despistado.- El tiempo de espera también es configurable: sin
callTimeout, un servidor colgado deja la pantalla con la rueda girando para siempre. - Un
401y un404se tratan distinto: el primero pide renovar la sesión, el segundo avisar y salir de la pantalla.