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:
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)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.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)
}