Warum beschwert sich Room über den Hauptthread? Fehleranalyse und 3 Lösungswege

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum ein "einfacher SELECT" zum Kopfzerbrechen wird

\n\n

Sie schreiben Ihre erste Android-App mit Room und Kotlin. Alles läuft hervorragend, bis es ans Auslesen von Daten aus der Datenbank geht. Sie schreiben so etwas wie:

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

Sie starten die App – und sie stürzt mit folgender Ausnahme ab:

\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

Kommt Ihnen das bekannt vor? Diese Frage auf Stack Overflow hat tausende Aufrufe und hunderte Kommentare gesammelt. Der Fehler erschreckt Anfänger, aber eigentlich ist es kein Bug – es ist ein Schutz vor Dummheit. Room (wie jedes gute ORM) verhindert, dass Sie versehentlich die Benutzeroberfläche blockieren.

\n\n

In diesem Artikel erklären wir warum das passiert und wie man SELECT-Abfragen in Android Room mit Kotlin korrekt ausführt. Sie lernen drei bewährte Ansätze kennen: LiveData, Coroutines und RxJava. Los geht's!

\n\n

Kurz und knapp: Warum blockiert Room den Hauptthread?

\n\n

Android ist standardmäßig ein Single-Thread-System. Die gesamte UI läuft auf dem main thread (Hauptthread). Wenn Sie auf diesem Thread aus der Datenbank lesen, friert die Oberfläche ein, bis die Abfrage abgeschlossen ist. Bei großen Datenmengen kann das Sekunden dauern – der Benutzer sieht eine "hängende" App.

\n\n

Room geht auf Nummer sicher: Es verbietet alle langen Operationen (Lesen, Schreiben) auf dem Hauptthread. Die Ausnahme ist, wenn Sie dies explizit über .allowMainThreadQueries() beim Erstellen der Datenbank erlauben. Das sollten Sie aber nicht tun – es ist ein Anti-Pattern.

\n\n

So sieht das typische Problem aus:

\n\n
// DAO\n@Dao\ninterface UserDao {\n    @Query("SELECT * FROM users WHERE id = :userId")\n    suspend fun getUserById(userId: Int): User  // suspend – schon ein Hinweis!\n}\n\n// Activity\nclass MainActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        // ❌ Fehler!\n        val user = database.userDao().getUserById(42)\n    }\n}
\n\n

Selbst wenn Sie die Funktion als suspend deklariert haben, aber ohne Coroutine aufrufen – Room wirft trotzdem eine Ausnahme. Beheben wir das!

\n\n

Ansatz 1: LiveData – einfach und reaktiv

\n\n

Der einfachste Weg ist, aus dem DAO LiveData zurückzugeben. Room verwaltet den Thread selbst: Die Abfrage wird im Hintergrund ausgeführt, das Ergebnis kommt in der UI an.

\n\n

Schritt 1: DAO ändern

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

Schritt 2: In Activity/Fragment abonnieren

\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

Vorteile: Minimaler Code, automatisches Abmelden bei Zerstörung der Activity, Reaktivität – Daten aktualisieren sich automatisch bei Änderungen in der Tabelle.

\n\n

Nachteile: LiveData ist an den Lebenszyklus gebunden, lässt sich nicht einfach mit anderen Datenquellen (Netzwerk, SharedPreferences) kombinieren.

\n\n

Ansatz 2: Coroutines – moderner Kotlin-Weg

\n\n

Wenn Sie Kotlin Coroutines verwenden (und das sollten Sie), ist dies der eleganteste Weg. Die Funktion wird als suspend markiert, und der Aufruf wird in eine Coroutine mit dem richtigen Dispatchers.IO verpackt.

\n\n

Schritt 1: DAO mit suspend

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

Schritt 2: Aufruf aus ViewModel (empfohlen)

\n\n
class MainVi                

Blogs

Buchempfehlungen