Fix for Chapter 12 build errors: "Not sure how to convert a Cursor" build error

Chapter 12 is one of the hardest chapters to get through - even for experienced programmers. I am referencing the eBook of Android Programming 5th Edition I purchased back in March. It’s listed as “Fifth edition, first printing, June 2022”. I recently re-downloaded it again. I’m on Android Studio “Giraffe” edition.

If you follow chapter 12 and type the changes for the code exactly as prescribed all the way before the conversion to Flow and StateFlow, you will eventually hit this build error:

...\CrimeDao.java:9: error: Not sure how to convert a Cursor to this method's return type (java.lang.Object).
    public abstract java.lang.Object getCrimes(@org.jetbrains.annotations.NotNull

OPTION 1 - update the Room libraries to latest

Change the Room runtime in your app/build.gradle from the book prescribed 2.4.2 version to the latest 2.6.1. That is, change this:

implementation("androidx.room:room-runtime:2.4.2")
implementation("androidx.room:room-ktx:2.4.2")
kapt("androidx.room:room-compiler:2.4.2")

To this:

implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")

And that seems to work. If this change works, you can likely stop here. Read on if not.

Option 2 - fix up the code so that Coroutines and DAO interop

My original solution before I discovered the above fix. And that is to to simply remove the suspend modifiers from the CrimeDao interface. Perhaps the latest tools and libraries just don’t like the mix of coroutines and DAO keywords

interface CrimeDao {
    @Query("SELECT * FROM crime")
    fun getCrimes(): List<Crime>
    @Query("SELECT * FROM crime WHERE id=(:id)")
    fun getCrime(id: UUID): Crime
}

That will of course fix the build error, but will instantly create a runtime error:

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

To workaround that, change these two functions in CrimeRespository to run on something other than the main thread. Change this.

suspend fun getCrimes(): List<Crime> = database.crimeDao().getCrimes()
suspend fun getCrime(id: UUID): Crime = database.crimeDao().getCrime(id)

To this:

suspend fun getCrimes(): List<Crime> {
    return withContext(Dispatchers.IO) {
        database.crimeDao().getCrimes()
    }
}

suspend fun getCrime(id: UUID) : Crime {
    return withContext(Dispatchers.IO) {
        database.crimeDao().getCrime(id)
    }
}

And then the build will work and the database will successfully display in the RecyclerView.

The above change seems clean to me. It keeps with the spirit of accessing the database on a background that ultimately resumes with the query result on the main thread.

Now as you continue into Chapter 12 and convert getCrimes to use the Flow model, all the suspend stuff gets removed from getCrimes, but not for getCrime(id). To be determined how this workaround will impact getCrime(id) in later chapters.

The bigger mystery is why the Chapter 12 reference implementation from https://www.bignerdranch.com/android-5e-solutions works without issue after unzipping the archive and building it. The Chapter 12 reference solution leaves CrimeDao.getCrime(id) as a suspend function, but doesn’t incur the build error like it would in my local project.

I suspect it’s because the reference implementation is using SDK 32 and Gradle version 7.x. The newer Android Studio builds defaults to new projects built with SDK 34 and Gradle version 8.x. That might explain the issue. But I haven’t had time to pinpoint where the build incompatibility is.

1 Like