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 inExamScoreDatabase.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 withinaddScore
and it worked. Any advice on what is correct would also be appreciated. Thanks.
CodePudding user response:
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)
}