Search code examples
androidkotlinandroid-room

Writing to database with Room causing app to crash


I've spent the day learning about how to write to databases using Room and have been attempting to write to one in my app. After completing an exercise, the user gets a score and can then press a button if they want to save the score. The button calls saveScore (see below). Logs show that it builds the database but then crashes after that with the error: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

What am I doing wrong?

    private fun saveScore(activityName: String, score: Int) {
        Log.v("AbdominalExamNew.kt", "saveScore button pressed")
        val dateAdded = Date()
        val newScore = ExamScore(0, activityName, dateAdded, score)
        val db = Room.databaseBuilder(applicationContext, ExamScoreDatabase::class.java, "examscore_database").build()
        Log.v("AbdominalExamew.kt","Database built")
        val examscoreDao = db.examscoreDao()
        examscoreDao.addScore(newScore)
    }

ExamScore.kt

package com.example.clinicalskills.database

import androidx.room.*

@Entity
data class ExamScore(
    @PrimaryKey(autoGenerate = true) val uid: Int,
    @ColumnInfo(name="exam") val currentExam: String?,
    @ColumnInfo(name="date") val dateAdded: java.util.Date,
    @ColumnInfo(name="score") val attemptScore: Int?
)

ExamScoreDao.kt

package com.example.clinicalskills.database

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

@Dao
interface ExamScoreDao {
    @Query("SELECT * FROM examscore")
    fun getAll(): List<ExamScore>

    @Query("SELECT * FROM examscore WHERE uid IN (:attemptRecord)")
    fun loadAllByIds(attemptRecord: IntArray): List<ExamScore>

    @Query("SELECT * FROM examscore WHERE exam = :selectedExam")
    fun viewScoresForExam(selectedExam: String): List<ExamScore>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun addScore(examScore: ExamScore)
}

ExamScoreDatabase.kt

package com.example.clinicalskills.database

import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters

@Database(entities = [ExamScore::class], version = 1, exportSchema = false)
@TypeConverters(TimeConverters::class)
abstract class ExamScoreDatabase : RoomDatabase() {
    abstract fun examscoreDao(): ExamScoreDao

    companion object {
        @Volatile
        private var INSTANCE: ExamScoreDatabase? = null

        fun getDatabase(context: Context): ExamScoreDatabase {
            Log.v("ExamScoreDatabase.kt", "getDatabase run")
            if (INSTANCE == null) {
                synchronized(this) {
                    INSTANCE = buildDatabase(context)
                }
            }
            return INSTANCE!!
        }

        private fun buildDatabase(context: Context): ExamScoreDatabase? {
            Log.v("ExamcoreDatabase.kt", "buildDatabase run")
            return Room.databaseBuilder(
                context.applicationContext,
                ExamScoreDatabase::class.java,
                "examscore_database"
                    ).build()
        }
    }
    }

Notes:

  • I tried to suspend addScore in ExamScoreDao but that caused a separate error and the app wouldn't compile (Suspend function 'addScore' should be called only from a coroutine or another suspend function)
  • There is code to build the database in two places: in the saveScore function and also in ExamScoreDatabase.kt - I wasn't sure where to add it: some code I found online put it in the database class, but Android didn't recognise the function if I added it here. So I tried within addScore and it worked. Any advice on what is correct would also be appreciated. Thanks.

Solution

  • As the documentation states Room doesn't allow access to the database from the main thread.

    So making your function a suspend function is one way to solve this. Assuming your saveScore function is inside your Activity you can do something like:

    lifecycleScope.launch {
        examscoreDao.addScore(newScore)
    }