Search code examples
androidkotlinandroid-roomandroid-jetpackandroid-jetpack-navigation

Android Room does not generate correct concrete Database_Ipml classes


I'm building a to-do list app in Android Studio using Jetpack:Room as the database library. I've followed the Android Developer tutorial to model my code, however I get an error that "java.lang.RuntimeException: Cannot find implementation for com.example.todolist.database.TaskRoomDatabase. TaskRoomDatabase_Impl does not exist" when I try to call the Insert function for the first time and create the database. I'm using Room version 2.5.2, ksp for the "room-compiler" and version 1_8 for java, and 2.7.3 for navigation.

Here is the task.kt class:

@Entity(tableName = "tasks")
data class Task(
    @PrimaryKey(autoGenerate = true)  val id: Int = 0,
    @ColumnInfo(name = "task_name") val taskName: String,
    @ColumnInfo(name = "task_description") val taskDescription: String
){}

TaskDao.kt class:

//Database Access Object to the Task entity database
@Dao
interface TaskDao {

    @Query("SELECT * from tasks WHERE task_name = :name")
    fun getTask(name: String): List<Task>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(task: Task) : Long

    @Update
    suspend fun update(task: Task)

    @Delete
    suspend fun delete(task: Task)
}

the TaskRoomDatabase.kt

@Database(entities = [Task::class], version = 1, exportSchema = false)
abstract class TaskRoomDatabase : RoomDatabase() {

    abstract fun taskDao(): TaskDao

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

        fun getDatabase(context: Context): TaskRoomDatabase {
            //if INSTANCE is not null, return it
            //else, create the database
            return INSTANCE ?: synchronized(this){
                val instance = Room.databaseBuilder(
                    context,
                    TaskRoomDatabase::class.java,
                    "task_database")
                    .createFromAsset("database/task_database")
                    .build()
                INSTANCE = instance

                instance
            }
        }
    }
}

and the function from the fragment that is supposed to call the insert function.

private fun addNewTask() : Boolean {
        if (isEntryValid()) {
            viewModel.addTask(
                binding.editTaskTitle.text.toString(),
                binding.taskDetails.text.toString()
            )
            return true
        }
        val toast = Toast.makeText(context, "Invalid Input", Toast.LENGTH_SHORT)
        toast.show()
        return false
    }

//then the viewModel.addtask function referenced above
fun addTask(taskName: String, taskDescription: String){
        Log.d("TaskView", "passed strings: $taskName  , $taskDescription  ")
        val newTask = getNewTask(taskName, taskDescription)
        insertTask(newTask)
    }

    //Insert a task object to DB in non-blocking coroutine
    private fun insertTask(newTask: Task) {
        viewModelScope.launch {
        taskDao.insert(newTask)
        }
    }

What am I doing wrong?

EDIT: Here is the build.gradle:

mport org.jetbrains.kotlin.storage.CacheResetOnProcessCanceled.enabled

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp") apply true
}

android {
    namespace = "com.example.todolist"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.todolist"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    buildFeatures{
        viewBinding = true
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }

}

dependencies {
    val room_version = "2.5.2"
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")

    implementation ("androidx.room:room-ktx:$room_version")
    //kapt ("androidx.room:room-compiler:$room_version")

    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.9.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation ("com.google.android.material:material:<version>")
    implementation ("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")
    val nav_version = "2.7.3"
    implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
    implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
    //Databinding to use DataBindingUtil
    implementation ("androidx.databinding:databinding-runtime:4.2.0")
    implementation("androidx.room:room-runtime:$room_version")
    annotationProcessor("androidx.room:room-compiler:$room_version")
    // To use Kotlin annotation processing tool (kapt)
    ksp("androidx.room:room-compiler:$room_version")
    // optional - Kotlin Extensions and Coroutines support for Room
    implementation("androidx.room:room-ktx:$room_version")
    // optional - RxJava2 support for Room
    implementation("androidx.room:room-rxjava2:$room_version")
    // optional - RxJava3 support for Room
    implementation("androidx.room:room-rxjava3:$room_version")
    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation("androidx.room:room-guava:$room_version")
    // optional - Test helpers
    testImplementation("androidx.room:room-testing:$room_version")
    // optional - Paging 3 Integration
    implementation("androidx.room:room-paging:$room_version")
}

When calling the function to add the bound text data to a Task object and then store it in a database, the program crashes and generates the error noted above.


Solution

  • As per the comments, there is no issue with your code (at least the database code). So the issue is likely with ksp.

    As a test, took your code, commented out .createFromAsset() and included .allowMainThreadQueries (for easy/simple testing).

    The main thing is that the java(generated) (as per Android View) included TaskRoomDatabase_Impl, a component that kapt/ksp builds.

    When run with a very basic activity just gets the db and then the dao into lateinits and then calls the getTask with an arbritrary string (no data so does not matter). This enabling App Inspection to be used which shows:-

    enter image description here

    For ksp the Project Build gradle used:-

    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    plugins {
        id 'com.android.application' version '7.2.1' apply false
        id 'com.android.library' version '7.2.1' apply false
        id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    • increased kotlin.android to 1.8.0

    The module Build Grade used:-

    plugins {
        id 'com.android.application'
        id 'org.jetbrains.kotlin.android'
        id 'com.google.devtools.ksp' version '1.8.0-1.0.8'
        //id 'kotlin-kapt'
    }
    
    android {
        compileSdk 33
    
        defaultConfig {
            applicationId "a.a.so77219401kotlinroomksp"
            minSdk 26
            targetSdk 32
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
    
        implementation 'androidx.core:core-ktx:1.7.0'
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.9.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        implementation 'androidx.room:room-ktx:2.5.2'
        implementation 'androidx.room:room-runtime:2.5.2'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
        ksp 'androidx.room:room-compiler:2.5.2'
    }
    
    • (as can be seen kapt was previously used (successfully) but commented out)
    • core-ktx is highlighted and is low at 1.7.0 but it works, 1.9.0 works, 1.12.0 requires at least compileSDK 34 (and then works).

    If changing from/to kapt from/to ksp I suggest that you do a Clean, then a Rebuild and the Compile.

    Activity code used to test:-

    class MainActivity : AppCompatActivity() {
        lateinit var db: TaskRoomDatabase
        lateinit var dao: TaskDao
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            db = TaskRoomDatabase.getDatabase(this)
            dao = db.taskDao()
            val x = dao.getTask("Blah")
        }
    }
    
    • allows App to stop/pause and App Inspection to be used

    TaskRoomDatabase code used for test :-

    @Database(entities = [Task::class], version = 1, exportSchema = false)
    abstract class TaskRoomDatabase : RoomDatabase() {
    
        abstract fun taskDao(): TaskDao
    
        companion object{
            @Volatile
            private var INSTANCE: TaskRoomDatabase? = null
    
            fun getDatabase(context: Context): TaskRoomDatabase {
                //if INSTANCE is not null, return it
                //else, create the database
                return INSTANCE ?: synchronized(this){
                    val instance = Room.databaseBuilder(
                        context,
                        TaskRoomDatabase::class.java,
                        "task_database")
                        //.createFromAsset("database/task_database")
                        .allowMainThreadQueries()
                        .build()
                    INSTANCE = instance
    
                    instance
                }
            }
        }
    }
    

    Android View - Explorer looks like:-

    enter image description here

    And just in case the gradle settings:-

    enter image description here