Pourquoi Room se plaint du thread principal ? Analyse de l'erreur et 3 façons de la résoudre

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : pourquoi un « simple SELECT » devient un casse-tête

\n\n

Vous écrivez votre première application Android avec Room et Kotlin. Tout se passe bien jusqu'à ce que vous deviez lire des données depuis la base. Vous écrivez quelque chose comme :

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

Vous lancez l'application — et elle plante avec l'exception :

\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

Ça vous dit quelque chose ? Cette question sur Stack Overflow a recueilli des milliers de vues et des centaines de commentaires. L'erreur effraie les débutants, mais en réalité ce n'est pas un bug — c'est une protection contre les erreurs. Room (comme toute ORM bien conçue) vous empêche de bloquer accidentellement l'interface utilisateur.

\n\n

Dans cet article, nous analyserons pourquoi cela se produit et comment exécuter correctement des requêtes SELECT dans Android Room avec Kotlin. Vous découvrirez trois approches éprouvées : LiveData, Coroutines et RxJava. C'est parti !

\n\n

L'essentiel en bref : pourquoi Room bloque le thread principal ?

\n\n

Android est un système monothread par défaut. Toute l'interface utilisateur fonctionne sur le main thread (thread principal). Si vous commencez à lire la base de données sur ce thread, l'interface se figera jusqu'à la fin de la requête. Avec de gros volumes de données, cela peut prendre plusieurs secondes — l'utilisateur verra une application « bloquée ».

\n\n

Room joue la sécurité : il interdit toute opération longue (lecture, écriture) sur le thread principal. Exception faite si vous l'autorisez explicitement via .allowMainThreadQueries() lors de la construction de la base. Mais il ne faut pas le faire — c'est un anti-patron.

\n\n

Voici à quoi ressemble le problème typique :

\n\n
// DAO\n@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    suspend fun getUserById(userId: Int): User  // suspend — déjà un indice !\n}\n\n// Activity\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        // ❌ Erreur !\n        val user = database.userDao().getUserById(42)\n    }\n}
\n\n

Même si vous avez rendu la fonction suspend, mais que vous l'appelez sans coroutine — Room lèvera quand même l'exception. On corrige ça !

\n\n

Approche 1 : LiveData — simple et réactif

\n\n

La façon la plus simple est de retourner LiveData depuis le DAO. Room gère lui-même le thread : la requête s'exécute en arrière-plan et le résultat arrive dans l'interface utilisateur.

\n\n

Étape 1. Modifier le DAO

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

Étape 2. S'abonner dans l'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

Avantages : code minimal, désabonnement automatique à la destruction de l'Activity, réactivité — les données se mettent à jour automatiquement lors des modifications de la table.

\n\n

Inconvénients : LiveData est liée au cycle de vie, difficile à combiner avec d'autres sources de données (réseau, SharedPreferences).

\n\n

Approche 2 : Coroutines — la méthode Kotlin moderne

\n\n

Si vous utilisez Kotlin Coroutines (et vous devriez), c'est la façon la plus élégante. La fonction est marquée comme suspend, et l'appel est encapsulé dans une coroutine avec le bon Dispatchers.IO.

\n\n

Étape 1. DAO avec suspend

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

Étape 2. Appel depuis le ViewModel (recommandé)

\n\n
class MainVi                

Blogs

Book Recommendations