Cómo migrar de Room a Realm en Android

Resumen

Migrar de Room a Realm en una app Android no implica reescribir tu lógica de negocio: basta con ajustar la capa de data, crear nuevos mappers y modificar la implementación de los repositories. Esta guía te muestra el proceso real, con los errores que aparecen en el camino y cómo resolverlos, ideal si trabajas con arquitectura limpia y quieres entender el impacto de cambiar de motor de persistencia.

¿Por qué crear mappers separados al migrar de Room a Realm?

La primera tarea consiste en aislar la conversión entre entidades de base de datos y objetos de dominio. Si ya tenías mappers para Room, no los borres: crea unos nuevos específicos para Realm y mantén la separación clara.

En la carpeta data necesitas funciones que conviertan en ambos sentidos. Para Realm, partimos de un OrderObject (la entidad de Realm) hacia un Order de dominio, y viceversa.

kotlin fun OrderObject.toDomain(): Order { return Order(id, customerName, item, total, imageUrl) }

El mapper inverso requiere atención especial. Como OrderObject no es una data class, no puedes usar el constructor directo: debes instanciar el objeto y aplicar los campos con apply.

kotlin fun Order.toRealm(): OrderObject { return OrderObject().apply { id = this@toRealm.id customerName = this@toRealm.customerName item = this@toRealm.item total = this@toRealm.total imageUrl = this@toRealm.imageUrl } }

El mismo patrón aplica para PreOrderObject, tanto al guardar como al leer. Esta separación te permite convivir con Room mientras pruebas Realm, sin romper la aplicación [01:15].

¿Qué es un mapper en arquitectura limpia? Es una función que traduce datos entre capas: por ejemplo, de una entidad de base de datos a un modelo de dominio. Evita que tu lógica de negocio dependa del motor de persistencia.

¿Cómo modificar el repository para usar Realm?

Con los mappers listos, el cambio en el repository es casi cosmético. En PreOrderRepositoryImpl, donde antes llamabas a savePreOrderRoom, ahora invocas la versión de Realm pasándole un PreOrderObject construido con apply.

Lo mismo aplica para listar, eliminar y recuperar:

  • getPreOrders cambia de preOrdersRoom a preOrdersRealm y reconoce automáticamente el mapper correcto.
  • deletePreOrder mantiene el mismo id, solo cambia la fuente.
  • retrievePreOrder conserva los parámetros y solo apunta a Realm.

Para OrderRepositoryImpl, el getOrders busca en LocalDataStorage la versión Realm. En el método de guardar (saveOrInsertOrderReal) sí hay un detalle extra: como recibe una lista, debes mapear cada elemento a la entidad de Realm antes de persistir. Y getOrderById simplemente cambia su fuente para que el compilador resuelva el mapeo a domain automáticamente [04:30].

¿Qué errores aparecen al migrar a Realm y cómo se resuelven?

Al correr la app por primera vez aparecen problemas que Room ocultaba. El más importante tiene que ver con las primary keys.

¿Por qué Realm lanza un error de primary key duplicada?

A diferencia de Room, Realm no soporta autoGenerate para claves primarias. Si tu objeto de dominio inicializa el id en cero, todos los registros que insertes tendrán el mismo id y Realm rechazará el segundo con un crash por consistencia.

La solución es generar el id manualmente en el momento de crear la entidad de Realm:

kotlin id = System.currentTimeMillis()

Usar el tiempo actual en milisegundos garantiza unicidad sin necesidad de un generador externo. Ese cambio se hace en el mapper de dominio a Realm, no en la capa de presentación [07:45].

¿Por qué System.currentTimeMillis funciona como primary key? Porque devuelve el tiempo actual en milisegundos del dispositivo, lo que produce valores únicos en inserciones secuenciales y evita conflictos de unicidad en Realm.

¿Cómo limpiar una base de datos Realm corrupta?

Si ya intentaste insertar registros con id = 0, la base de datos local quedó con conflictos. La forma más rápida de empezar de cero es ir a Ajustes del dispositivo, buscar la app, entrar a Info y limpiar el storage, o desinstalar la app para borrar la caché completamente.

¿Cómo evitar que un crash de Realm tumbe la app?

Cuando una excepción de Realm sube hasta la UI, el usuario ve un cierre forzado. Envuelve la operación de guardado en un runCatching para capturar el error y reportarlo sin que la app crashee:

kotlin runCatching { localStorage.savePreOrderRealm(preOrder.toRealm()) }.onFailure { /* reportar */ }

Esto te permite manejar la excepción y mostrar feedback útil al usuario [10:20].

¿Qué cambios reales viste al alternar entre Room y Realm?

La prueba final consiste en crear preórdenes en ambos motores y alternar la fuente del getOrders. Al guardar dos registros en Room (un control PS5 para Julián y un iPad para Carlos) y luego cambiar una sola línea en getOrders de roomLocal a realmLocal, la lista que ve el usuario cambia por completo.

Ese es el punto: con la capa de dominio intacta, el motor de base de datos se vuelve un detalle reemplazable. La sincronización con el servicio remoto sigue funcionando igual, porque el repositorio sigue exponiendo objetos de dominio.

Algunos puntos clave para que tomes una mejor decisión antes de migrar:

  • Room ofrece autoGenerate para primary keys; Realm no.
  • Realm exige instanciar entidades con apply porque no son data classes.
  • Los mappers duplicados son una inversión que paga al permitir convivencia entre motores.
  • Limpiar el storage del dispositivo es parte normal del proceso de depuración con Realm.

¿Has migrado entre motores de persistencia en Android? Cuéntame en los comentarios qué motor estás usando y por qué.