¿Por qué Room se queja del hilo principal? Analizamos el error y 3 formas de solucionarlo

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué un «SELECT simple» se convierte en un dolor de cabeza

\n\n

Estás escribiendo tu primera aplicación en Android con Room y Kotlin. Todo va bien hasta que llega el momento de leer datos de la base de datos. Escribes algo como:

\n\n
val user = userDao.getUserById(42)\ntextView.text = user.name
\n\n

Lo ejecutas — y la aplicación falla con la excepción:

\n\n
java.lang.IllegalStateException: Cannot access database on the main thread\nsince it may potentially lock the UI for a long period of time.
\n\n

¿Te suena? Esta pregunta en Stack Overflow ha acumulado miles de visitas y cientos de comentarios. El error asusta a los principiantes, pero en realidad no es un bug, es una protección contra tontos. Room (como cualquier ORM competente) no te permite bloquear la interfaz de usuario accidentalmente.

\n\n

En este artículo analizaremos por qué ocurre esto y cómo ejecutar correctamente consultas SELECT en Android Room con Kotlin. Conocerás tres enfoques probados: LiveData, Coroutines y RxJava. ¡Allá vamos!

\n\n

Brevemente sobre lo principal: ¿por qué Room bloquea el hilo principal?

\n\n

Android es un sistema de un solo hilo por defecto. Toda la UI funciona en el main thread (hilo principal). Si comienzas a leer la base de datos en este hilo, la interfaz se congelará hasta que la consulta termine. Con grandes volúmenes de datos, esto puede tomar segundos — el usuario verá una aplicación «colgada».

\n\n

Room se asegura: prohíbe cualquier operación larga (lectura, escritura) en el hilo principal. La excepción es si lo permites explícitamente mediante .allowMainThreadQueries() al construir la base de datos. Pero no deberías hacerlo — es un antipatrón.

\n\n

Así es como se ve el problema típico:

\n\n
// DAO\n@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    suspend fun getUserById(userId: Int): User  // suspend — ¡ya es una pista!\n}\n\n// Activity\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        // ❌ ¡Error!\n        val user = database.userDao().getUserById(42)\n    }\n}
\n\n

Incluso si hiciste la función suspend, pero la llamas sin una corrutina — Room igual lanzará la excepción. ¡Lo solucionamos!

\n\n

Enfoque 1: LiveData — simple y reactivo

\n\n

La forma más sencilla es devolver LiveData desde el DAO. Room mismo gestiona el hilo: la consulta se ejecuta en segundo plano y el resultado llega a la UI.

\n\n

Paso 1. Cambiamos el DAO

\n\n
@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    fun getUserById(userId: Int): LiveData<User>\n}
\n\n

Paso 2. Nos suscribimos en Activity/Fragment

\n\n
class MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        database.userDao().getUserById(42).observe(this) { user ->\n            textView.text = user.name\n        }\n    }\n}
\n\n

Ventajas: mínimo código, cancelación automática de suscripción al destruirse la Activity, reactividad — los datos se actualizan solos al cambiar la tabla.

\n\n

Desventajas: LiveData está ligada al ciclo de vida, no se puede combinar fácilmente con otras fuentes de datos (red, SharedPreferences).

\n\n

Enfoque 2: Coroutines — la forma moderna de Kotlin

\n\n

Si usas Kotlin Coroutines (y deberías), esta es la forma más elegante. La función se marca como suspend y la llamada se envuelve en una corrutina con el Dispatchers.IO correcto.

\n\n

Paso 1. DAO con suspend

\n\n
@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    suspend fun getUserById(userId: Int): User\n}
\n\n

Paso 2. Llamada desde ViewModel (recomendado)

\n\n
class MainVi                

Blogs

Book Recommendations