Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Room Kotlin - Query in background thread - return value problem

I have converted a sample program from Java/SQLite to Kotlin/Room.

I am struggling to implement queries with return values in a background thread.

This has been asked but I could not get it to work. I have read answers to similar questions but some are deprecated, or some solutions seem complex for something that should be trivial.

I am really stumped in coming up with a simple solution when I need to use the return value of the query.

(Everything works as it should if I force doing queries in the main thread with allowMainThreadQueries())

This is one of the functions that I would like to have do the query in a background thread:

fun getCrimes(): List<Crime> {
    val crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    return crimes
}

I can call the function like follows and it works but it would mean that I need to add Async calls throughout other classes and it does not seem elegant:

AsyncTask.execute {
    mCrimes = getCrimes() as ArrayList<Crime>
}

==> I would like to modify getCrimes itself to have it run the query in the background, like: (incorrect code follows)

fun getCrimes(): List<Crime> {
    var crimes: ArrayList<Crime>

    AsyncTask.execute {
        crimes = crimesDAO.getAllCrimes() as ArrayList<Crime>
    }

    return crimes // This is wrong as crimes in not initialized
}

I looked into kotlin coroutines, Live Data and rxjava but could not find a simple way around this.

Background info:

This is the data class:

@Entity(tableName = "crimes_table")
class Crime {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="id")
    var id: Long = 0

    @ColumnInfo(name="uuid")
    @TypeConverters(UUIDConverter::class)
    var mId: UUID = UUID.randomUUID()

    @ColumnInfo(name="title")
    var mTitle: String? = null

    @ColumnInfo(name="date")
    @TypeConverters(DateConverter::class)
    var mDate: Date? = Date()

    @ColumnInfo(name="solved")
    var mSolved: Boolean = false
}

This is the DAO:

@Dao
interface CrimesListDAO {

    @Query("SELECT * FROM crimes_table")
    fun getAllCrimes(): List<Crime>

    @Query("SELECT * FROM crimes_table WHERE uuid = :uuidString LIMIT 1")
    fun getOneCrime(uuidString: String): Crime

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertCrime(crime: Crime)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    fun updateCrime(crime: Crime)

    @Delete
    fun deleteCrime(crime: Crime)
}

This is the DatabaseApp class:

@Database(entities = [(Crime::class)], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun crimesListDAO(): CrimesListDAO
}

This is how I instantiate the database:

class ApplicationContextProvider : Application() {
    ...
    companion object {
        var database: AppDatabase? = null
    ...
    }

    override fun onCreate() {
        super.onCreate()
        ApplicationContextProvider.database = Room.databaseBuilder(this, AppDatabase::class.java, "crimeBase.db").build()
    }
}
like image 347
Will Avatar asked Oct 10 '18 18:10

Will


2 Answers

Step One

Turn your blocking function

fun getCrimes() = crimesDAO.getAllCrimes() as List<Crime>

into a suspending one:

suspend fun getCrimes() = withContext(Dispatchers.IO) { 
    crimesDAO.getAllCrimes() as List<Crime>
}

Step Two

In order to call a suspendable function you must first launch a coroutine:

override fun onSomeEvent() {
    (context as LifecycleOwner).lifecycleScope.launch {
        val crimes = getCrimes()
        // work with crimes
    }
}

This works as long as your context is not a legacy activity/fragment class. Modern classes, such as AppCompatActivity, implement LifecycleOwner.

like image 93
Marko Topolnik Avatar answered Nov 15 '22 14:11

Marko Topolnik


UPDATE: My answer is wrong. (thanks Marko) It does start another background thread, BUT it still blocks the UI Thread. So this circumvents Room protection to not make calls in UI thread, but it defeats the purpose.

I used the below code to confirm that I was generating a new thread but blocking the caller thread anyway:

fun main(args: Array<String>) {
    exampleBlockingDispatcher()
}

suspend fun printlnDelayed(message: String) {
    delay(2000)
    println(message)
}

// Running on another thread but still blocking the main thread
fun exampleBlockingDispatcher(){
    runBlocking(Dispatchers.Default) {
        println("one - from thread ${Thread.currentThread().name}")
        printlnDelayed("two - from thread ${Thread.currentThread().name}")
    }
    // Outside of runBlocking to show that it's running in the blocked main thread
    println("three - from thread ${Thread.currentThread().name}")
    // It still runs only after the runBlocking is fully executed.
}

Original Answer:

After many hours I figured it out (edit: I wish). The correct way to call the DAO methods using coroutines in a background thread and be able to have a value returned is:

fun getCrimes(): ArrayList<Crime> = runBlocking(Dispatchers.Default) {
    val result = async { crimesDAO.getAllCrimes() }.await()
    return@runBlocking result as ArrayList<Crime>
}

Read a lot of code & tutorials but by far the best I recommend on coroutines is this one:

https://resocoder.com/2018/10/06/kotlin-coroutines-tutorial-stable-version-async-await-withcontext-launch/

It has a lot of examples with simple code that helps to grasp details and see / try them in action.

like image 36
Will Avatar answered Nov 15 '22 12:11

Will