Implementación final del PreOrderRepository

Resumen

Implementar el PreOrderRepository cierra la capa de data en una app Android construida con Clean Architecture, Room y Hilt. Aquí verás cómo orquestar el guardado remoto y local, mapear entidades hacia dominio y exponer el repositorio mediante inyección de dependencias para que tu capa de presentación lo consuma sin fricción.

Qué hace el PreOrderRepository en la capa de data

El repositorio actúa como puente entre el remote data storage y el local storage, decidiendo cuándo sincronizar con el servicio remoto y cuándo persistir localmente con Room.

Dentro de la carpeta data se crea la clase PreOrderRepositoryImpl, que implementa la interfaz PreOrderRepository definida en domain. El constructor recibe dos dependencias clave que luego Hilt inyectará: el remote data storage y el local storage. Esa separación permite que la lógica de negocio no sepa si los datos vienen de la red o de la base de datos.

¿Qué es un repositorio en Clean Architecture? Es la clase que centraliza el acceso a datos y oculta a la capa de dominio si la información viene de una API, de Room o de cualquier otra fuente.

Cómo guardar una preorden con savePreOrder y manejar el flag de sincronización

El método savePreOrder ejecuta primero el guardado remoto y luego decide cómo persistir localmente.

La secuencia funciona así:

  • Se llama a remote.savePreOrder(preOrder) y se captura la respuesta con un also que entrega un result.
  • Se construye una PreOrderEntity para Room con el preOrderId, el customerName y el product.
  • Se agrega un flag booleano que indica si la sincronización remota fue exitosa.
  • Se llama a localStorage.savePreOrderInRoom(entity) para almacenarla.

Ese flag es la pieza que después permite reintentar el envío si la red falló. Si result fue exitoso, el flag queda en true; si no, queda en false y la preorden espera su retry.

Cómo recuperar las preórdenes locales y mapearlas a dominio

El método de lectura retorna las preórdenes guardadas en Room y las transforma a entidades de dominio. La cadena es directa: localStorage devuelve las preórdenes desde Room, se envuelve la operación en un Room caching para capturar excepciones y se aplica un map { it.toDomain() }.

Ese toDomain() vive en una clase de mappers. Es una static function que convierte una PreOrderEntity de base de datos en un PreOrder de dominio, copiando el id, el customerName, el product y el flag de sincronización. Sin ese mapper, la capa de domain quedaría acoplada a Room, y eso rompería la regla de dependencia.

¿Por qué mapear entidades de Room a dominio? Porque la capa de dominio debe ser independiente del framework. Si cambias Room por otra base de datos, solo ajustas los mappers, no el dominio.

Cómo eliminar y reintentar preórdenes desde el repositorio

El borrado y el retry son los dos métodos que cierran el ciclo de vida de una preorden offline.

Cómo funciona el delete por ID en Room

El deletePreOrder no retorna nada, así que se invoca directamente: localStorage.delete(id) sobre Room. Es la operación más simple del repositorio, pero crítica para mantener limpia la base local cuando una preorden ya cumplió su ciclo.

Cómo reintentar la sincronización con retry

El método retry toma una preorden que quedó marcada como no sincronizada y vuelve a intentar enviarla al servicio remoto:

  1. Llama a remote.savePreOrder(preOrder) y guarda la respuesta en un val result.
  2. Si la respuesta es exitosa, ejecuta un update en Room cargando la preorden por su ID.
  3. Cambia el flag a true para reflejar que la sincronización ya ocurrió.

Esa lógica habilita un patrón offline-first: la app funciona aunque no haya conexión, y los datos se reconcilian cuando vuelve la red.

Cómo inyectar el repositorio con Hilt en el módulo de DI

Para que el repositorio esté disponible en toda la app, se registra en un módulo de Hilt dentro de la carpeta DI.

El módulo se llama RepositoryModule y lleva las anotaciones habituales de Hilt:

  • @Module para declararlo como módulo.
  • @InstallIn(SingletonComponent::class) para que viva durante toda la app.
  • @Provides y @Singleton en cada función que expone un repositorio.

Dentro del módulo se declaran dos provides. El primero entrega un OrderRepository recibiendo RemoteDataStorage y LocalStorage como parámetros. El segundo, idéntico en estructura, entrega un PreOrderRepository con esas mismas dos dependencias. Ambas fuentes ya tienen @Inject en su constructor, por lo que Hilt las resuelve automáticamente en el grafo de dependencias.

¿Para qué sirve @InstallIn(SingletonComponent::class)? Indica a Hilt que las dependencias de ese módulo viven mientras viva la aplicación, evitando crear múltiples instancias del mismo repositorio.

Con esa configuración queda cerrada la capa de data: tienes un repositorio que combina remoto y local, mapea entidades hacia dominio, soporta retry y se inyecta limpiamente. El siguiente paso natural es la capa de presentación y UI, donde estos repositorios se consumen desde ViewModels.

¿En tu app prefieres una estrategia offline-first como esta o sincronización en tiempo real? Cuéntame en los comentarios cómo manejas el flag de sincronización en tus repositorios.